├── .dependabot
└── config.yml
├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ ├── custom.md
│ └── feature_request.md
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── PULL_REQUEST_TEMPLATE.md
├── README.md
├── _headers
├── gatsby-config.js
├── gatsby-node.js
├── netlify.toml
├── netlify
└── functions
│ └── hello.js
├── package-lock.json
├── package.json
├── renovate.json
├── src
├── cms
│ ├── cms.js
│ └── preview-templates
│ │ ├── AboutPagePreview.js
│ │ ├── BlogPostPreview.js
│ │ ├── IndexPagePreview.js
│ │ └── ProductPagePreview.js
├── components
│ ├── BlogRoll.js
│ ├── Content.js
│ ├── Features.js
│ ├── Footer.js
│ ├── FullWidthImage.js
│ ├── Layout.js
│ ├── Navbar.js
│ ├── PreviewCompatibleImage.js
│ ├── Pricing.js
│ ├── SiteMetadata.js
│ ├── Testimonials.js
│ └── all.sass
├── img
│ ├── github-icon.svg
│ ├── logo.svg
│ └── social
│ │ ├── facebook.svg
│ │ ├── instagram.svg
│ │ ├── twitter.svg
│ │ └── vimeo.svg
├── pages
│ ├── 404.js
│ ├── about
│ │ └── index.md
│ ├── blog
│ │ ├── 2016-12-17-making-sense-of-the-scaas-new-flavor-wheel.md
│ │ ├── 2017-01-04-a-beginners-guide-to-brewing-with-chemex.md
│ │ ├── 2017-01-04-just-in-small-batch-of-jamaican-blue-mountain-in-store-next-week.md
│ │ └── index.js
│ ├── contact
│ │ ├── examples.js
│ │ ├── file-upload.js
│ │ ├── index.js
│ │ └── thanks.js
│ ├── index.md
│ ├── products
│ │ └── index.md
│ └── tags
│ │ └── index.js
└── templates
│ ├── about-page.js
│ ├── blog-post.js
│ ├── index-page.js
│ ├── product-page.js
│ └── tags.js
└── static
├── admin
└── config.yml
└── img
├── apple-touch-icon.png
├── blog-index.jpg
├── chemex.jpg
├── coffee-gear.png
├── coffee.png
├── favicon-16x16.png
├── favicon-32x32.png
├── flavor_wheel.jpg
├── home-jumbotron.jpg
├── jumbotron.jpg
├── logo.svg
├── meeting-space.png
├── og-image.jpg
├── products-full-width.jpg
├── products-grid1.jpg
├── products-grid2.jpg
├── products-grid3.jpg
├── safari-pinned-tab.svg
└── tutorials.png
/.dependabot/config.yml:
--------------------------------------------------------------------------------
1 | version: 1
2 |
3 | update_configs:
4 | - package_manager: javascript
5 | directory: /
6 | update_schedule: live
7 | allowed_updates:
8 | - match:
9 | update_type: security
10 | automerged_updates:
11 | - match:
12 | dependency_type: all
13 | update_type: in_range
14 | version_requirement_updates: widen_ranges
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ---
11 | name: Bug report
12 | about: Create a report to help us improve
13 | ---
14 |
15 |
16 |
17 |
18 | # Bug report
19 |
20 |
21 |
22 |
23 |
24 |
25 | **What is the current behavior?**
26 |
27 |
28 | **If the current behavior is a bug, please provide the steps to reproduce.**
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 | **What is the expected behavior?**
38 |
39 |
40 |
41 |
42 |
43 | **Other relevant information:**
44 |
45 |
46 |
47 | Node.js version:
48 | NPM/Yarn version
49 | Operating System:
50 | Additional tools:
51 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/custom.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Custom issue template
3 | about: Describe this issue template's purpose here.
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ---
11 | name: Other
12 | about: Something else
13 |
14 | ---
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | ---
11 | name: Feature request
12 | about: Suggest an idea for this project
13 |
14 | ---
15 |
16 |
17 |
18 | ## Feature request
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | **What is the expected behavior?**
27 |
28 |
29 | **What is motivation or use case for adding/changing the behavior?**
30 |
31 |
32 | **How should this be implemented in your opinion?**
33 |
34 |
35 | **Are you willing to work on this yourself?**
36 | yes
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Project dependencies
2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
3 | node_modules
4 | .cache/
5 | # Build directory
6 | public/
7 | static/admin/*.bundle.*
8 | .DS_Store
9 | yarn-error.log
10 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at david@netlify.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # CONTRIBUTING
2 |
3 | Contributions are always welcome, no matter how large or small. Before contributing,
4 | please read the [code of conduct](CODE_OF_CONDUCT.md).
5 |
6 | ## Setup
7 |
8 | > Install yarn on your system: [https://yarnpkg.com/en/docs/install](https://yarnpkg.com/en/docs/install)
9 |
10 | ### Install dependencies
11 |
12 | > Only required on the first run, subsequent runs can use `yarn` to both
13 | bootstrap and run the development server using `yarn develop`.
14 | Since this starter using the [netlify-dev](https://www.netlify.com/products/dev/#how-it-works), there could be further issues you, please check the [netlify-dev](https://github.com/netlify/netlify-dev) repository for further information and set up questions.
15 |
16 | ```sh
17 | $ git clone https://github.com/netlify-templates/gatsby-starter-netlify-cms
18 | $ yarn
19 | ```
20 |
21 | ## Available scripts
22 |
23 |
24 | ### `build`
25 |
26 | Build the static files into the `public` folder, turns lambda functions into a deployable form.
27 |
28 | #### Usage
29 |
30 | ```sh
31 | $ yarn build
32 | ```
33 |
34 | ### `clean`
35 |
36 | Runs `gatsby clean` command.
37 |
38 | #### Usage
39 |
40 | ```sh
41 | yarn clean
42 | ```
43 |
44 | ### `netlify dev`
45 |
46 | Starts the netlify dev environment, including the gatsby dev environment.
47 | For more infor check the [Netlify Dev Docs](https://github.com/netlify/cli/blob/master/docs/netlify-dev.md)
48 |
49 | ```sh
50 | netlify dev
51 | ```
52 |
53 | ### `develop` or `start`
54 |
55 | Runs the `clean` script and starts the gatsby develop server using the command `gatsby develop`. We recomend using this command when you don't need Netlify specific features
56 |
57 | #### Usage
58 |
59 | ```sh
60 | yarn develop
61 | ```
62 | ### `test`
63 |
64 | Not implmented yet
65 |
66 | #### Usage
67 |
68 | ```sh
69 | yarn test
70 | ```
71 |
72 | ### `format`
73 |
74 | Formats code and docs according to our style guidelines using `prettier`
75 |
76 | #### Usage
77 |
78 | ```sh
79 | yarn format
80 | ```
81 |
82 |
83 | ## Pull Requests
84 |
85 | We actively welcome your pull requests!
86 |
87 | If you need help with Git or our workflow, please ask on [Gitter.im](https://gitter.im/netlify/NetlifyCMS). We want your contributions even if you're just learning Git. Our maintainers are happy to help!
88 |
89 | Netlify CMS uses the [Forking Workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/forking-workflow) + [Feature Branches](https://www.atlassian.com/git/tutorials/comparing-workflows/feature-branch-workflow). Additionally, PR's should be [rebased](https://www.atlassian.com/git/tutorials/merging-vs-rebasing) on master when opened, and again before merging.
90 |
91 | 1. Fork the repo.
92 | 2. Create a branch from `master`. If you're addressing a specific issue, prefix your branch name with the issue number.
93 | 2. If you've added code that should be tested, add tests.
94 | 3. If you've changed APIs, update the documentation.
95 | 4. Run `yarn test` and ensure the test suite passes. (Not applicable yet)
96 | 5. Use `yarn format` to format and lint your code.
97 | 6. PR's must be rebased before merge (feel free to ask for help).
98 | 7. PR should be reviewed by two maintainers prior to merging.
99 |
100 | ## License
101 |
102 | By contributing to the Gatsby - Netlify CMS starter, you agree that your contributions will be licensed
103 | under its [MIT license](LICENSE).
104 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 gatsbyjs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | **What kind of change does this PR introduce?**
9 |
10 |
11 |
12 | **Does this PR introduce a breaking change?**
13 |
14 |
15 |
16 | **What needs to be documented once your changes are merged?**
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Gatsby + Netlify CMS Starter
2 |
3 | [](https://app.netlify.com/sites/gatsby-starter-netlify-cms-ci/deploys)
4 |
5 | **Note:** This starter uses [Gatsby v4](https://www.gatsbyjs.com/gatsby-4/).
6 |
7 | This repo contains an example business website that is built with [Gatsby](https://www.gatsbyjs.org/), and [Netlify CMS](https://www.netlifycms.org): **[Demo Link](https://gatsby-netlify-cms.netlify.com/)**.
8 |
9 | It follows the [JAMstack architecture](https://jamstack.org) by using Git as a single source of truth, and [Netlify](https://www.netlify.com) for continuous deployment, and CDN distribution.
10 |
11 | ## Features
12 |
13 | - A simple landing page with blog functionality built with Netlify CMS
14 | - Editable Pages: Landing, About, Product, Blog-Collection and Contact page with Netlify Form support
15 | - Create Blog posts from Netlify CMS
16 | - Tags: Separate page for posts under each tag
17 | - Basic directory organization
18 | - Uses Bulma for styling, but size is reduced by `gatsy-plugin-purgecss`
19 | - Blazing fast loading times thanks to pre-rendered HTML and automatic chunk loading of JS files
20 | - Uses `gatsby-plugin-image` with Netlify-CMS preview support
21 | - Separate components for everything
22 | - Netlify deploy configuration
23 | - Netlify function support, see `netlify/functions` folder
24 | - Perfect score on Lighthouse for SEO, Accessibility and Performance (wip:PWA)
25 | - ..and more
26 |
27 | ## Prerequisites
28 |
29 | - Minimal Node.js version 14.15.0
30 | - [Gatsby CLI](https://www.gatsbyjs.com/docs/reference/gatsby-cli/)
31 | - [Netlify CLI](https://github.com/netlify/cli)
32 |
33 | ## Getting Started (Recommended)
34 |
35 | Netlify CMS can run in any frontend web environment, but the quickest way to try it out is by running it on a pre-configured starter site with Netlify. The example here is the Kaldi coffee company template (adapted from [One Click Hugo CMS](https://github.com/netlify-templates/one-click-hugo-cms)). Use the button below to build and deploy your own copy of the repository:
36 |
37 |
38 |
39 | After clicking that button, you’ll authenticate with GitHub and choose a repository name. Netlify will then automatically create a repository in your GitHub account with a copy of the files from the template. Next, it will build and deploy the new site on Netlify, bringing you to the site dashboard when the build is complete. Next, you’ll need to set up Netlify’s Identity service to authorize users to log in to the CMS.
40 |
41 | ### Access Locally
42 |
43 | Pulldown a local copy of the Github repository Netlify created for you, with the name you specified in the previous step
44 |
45 | ```
46 | $ git clone https://github.com/[GITHUB_USERNAME]/[REPO_NAME].git
47 | $ cd [REPO_NAME]
48 | $ yarn
49 | $ netlify dev # or ntl dev
50 | ```
51 |
52 | This uses [Netlify Dev](https://www.netlify.com/products/dev/?utm_source=blog&utm_medium=netlifycms&utm_campaign=devex) CLI feature to serve any functions you have in the `netlify/functions` folder.
53 |
54 | To test the CMS locally, you'll need to run a production build of the site:
55 |
56 | ```
57 | $ npm run build
58 | $ netlify dev # or ntl dev
59 | ```
60 |
61 | ### Media Libraries (installed, but optional)
62 |
63 | Media Libraries have been included in this starter as a default. If you are not planning to use `Uploadcare` or `Cloudinary` in your project, you **can** remove them from module import and registration in `src/cms/cms.js`. Here is an example of the lines to comment or remove them your project.
64 |
65 | ```javascript
66 | import CMS from "netlify-cms-app";
67 | // import uploadcare from 'netlify-cms-media-library-uploadcare'
68 | // import cloudinary from 'netlify-cms-media-library-cloudinary'
69 |
70 | import AboutPagePreview from "./preview-templates/AboutPagePreview";
71 | import BlogPostPreview from "./preview-templates/BlogPostPreview";
72 | import ProductPagePreview from "./preview-templates/ProductPagePreview";
73 | import IndexPagePreview from "./preview-templates/IndexPagePreview";
74 |
75 | // CMS.registerMediaLibrary(uploadcare);
76 | // CMS.registerMediaLibrary(cloudinary);
77 |
78 | CMS.registerPreviewTemplate("index", IndexPagePreview);
79 | CMS.registerPreviewTemplate("about", AboutPagePreview);
80 | CMS.registerPreviewTemplate("products", ProductPagePreview);
81 | CMS.registerPreviewTemplate("blog", BlogPostPreview);
82 | ```
83 |
84 | Note: Don't forget to also remove them from `package.json` and `yarn.lock` / `package-lock.json` using `yarn` or `npm`. During the build netlify-cms-app will bundle the media libraries as well, having them removed will save you build time.
85 | Example:
86 |
87 | ```
88 | yarn remove netlify-cms-media-library-uploadcare
89 | ```
90 |
91 | OR
92 |
93 | ```
94 | yarn remove netlify-cms-media-library-cloudinary
95 | ```
96 |
97 | ## Getting Started (Without Netlify)
98 |
99 | ```
100 | $ gatsby new [SITE_DIRECTORY_NAME] https://github.com/netlify-templates/gatsby-starter-netlify-cms/
101 | $ cd [SITE_DIRECTORY_NAME]
102 | $ npm run build
103 | $ npm run start
104 | ```
105 |
106 | ### Setting up the CMS
107 |
108 | Follow the [Netlify CMS Quick Start Guide](https://www.netlifycms.org/docs/quick-start/#authentication) to set up authentication, and hosting for production.
109 |
110 | If you want use Netlify CMS locally, run the site in one terminal with `npm run start` and in another
111 | Terminal you can use `npx netlify-cms-proxy-server` which proxy requests so you'll be automatically logged
112 | in as a user on [http:localhost:3000/admin](http:localhost:3000/admin).
113 |
114 | ## Debugging
115 |
116 | Windows users, who aren't using [WSL](https://docs.microsoft.com/en-us/windows/wsl/about), might encounter `node-gyp` errors when trying to npm install.
117 | To resolve, make sure that you have both Python 2.7 and the Visual C++ build environment installed.
118 |
119 | ```
120 | npm config set python python2.7
121 | npm install --global --production windows-build-tools
122 | ```
123 |
124 | [Full details here](https://www.npmjs.com/package/node-gyp "NPM node-gyp page").
125 |
126 | MacOS and WSL users who might also encounter some errors, check [node-gyp](https://github.com/nodejs/node-gyp) for more info. We recommend using the latest stable node version.
127 |
128 | ## Purgecss
129 |
130 | This plugin uses [gatsby-plugin-purgecss](https://www.gatsbyjs.org/packages/gatsby-plugin-purgecss/) and [bulma](https://bulma.io/). The bulma builds are usually ~170K but reduced 90% by purgecss.
131 |
132 | # CONTRIBUTING
133 |
134 | Contributions are always welcome, no matter how large or small. Before contributing,
135 | please read the [code of conduct](CODE_OF_CONDUCT.md).
136 |
--------------------------------------------------------------------------------
/_headers:
--------------------------------------------------------------------------------
1 | /static/*
2 | Cache-Control: "public, max-age=360000"
3 |
--------------------------------------------------------------------------------
/gatsby-config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | siteMetadata: {
3 | title: "Gatsby + Netlify CMS Starter",
4 | description:
5 | "This repo contains an example business website that is built with Gatsby, and Netlify CMS.It follows the JAMstack architecture by using Git as a single source of truth, and Netlify for continuous deployment, and CDN distribution.",
6 | },
7 | plugins: [
8 | "gatsby-plugin-react-helmet",
9 | {
10 | resolve: "gatsby-plugin-sass",
11 | options: {
12 | sassOptions: {
13 | indentedSyntax: true,
14 | },
15 | },
16 | },
17 | {
18 | // keep as first gatsby-source-filesystem plugin for gatsby image support
19 | resolve: "gatsby-source-filesystem",
20 | options: {
21 | path: `${__dirname}/static/img`,
22 | name: "uploads",
23 | },
24 | },
25 | {
26 | resolve: "gatsby-source-filesystem",
27 | options: {
28 | path: `${__dirname}/src/pages`,
29 | name: "pages",
30 | },
31 | },
32 | {
33 | resolve: "gatsby-source-filesystem",
34 | options: {
35 | path: `${__dirname}/src/img`,
36 | name: "images",
37 | },
38 | },
39 | `gatsby-plugin-image`,
40 | "gatsby-plugin-sharp",
41 | "gatsby-transformer-sharp",
42 | {
43 | resolve: "gatsby-transformer-remark",
44 | options: {
45 | plugins: [
46 | 'gatsby-remark-relative-images',
47 | {
48 | resolve: "gatsby-remark-images",
49 | options: {
50 | // It's important to specify the maxWidth (in pixels) of
51 | // the content container as this plugin uses this as the
52 | // base for generating different widths of each image.
53 | maxWidth: 2048,
54 | },
55 | },
56 | {
57 | resolve: "gatsby-remark-copy-linked-files",
58 | options: {
59 | destinationDir: "static",
60 | },
61 | },
62 | ],
63 | },
64 | },
65 | {
66 | resolve: "gatsby-plugin-netlify-cms",
67 | options: {
68 | modulePath: `${__dirname}/src/cms/cms.js`,
69 | },
70 | },
71 | {
72 | resolve: "gatsby-plugin-purgecss", // purges all unused/unreferenced css rules
73 | options: {
74 | develop: true, // Activates purging in npm run develop
75 | purgeOnly: ["/all.sass"], // applies purging only on the bulma css file
76 | },
77 | }, // must be after other CSS plugins
78 | "gatsby-plugin-netlify", // make sure to keep it last in the array
79 | ],
80 | };
81 |
--------------------------------------------------------------------------------
/gatsby-node.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash')
2 | const path = require('path')
3 | const { createFilePath } = require('gatsby-source-filesystem')
4 |
5 | exports.createPages = ({ actions, graphql }) => {
6 | const { createPage } = actions
7 |
8 | return graphql(`
9 | {
10 | allMarkdownRemark(limit: 1000) {
11 | edges {
12 | node {
13 | id
14 | fields {
15 | slug
16 | }
17 | frontmatter {
18 | tags
19 | templateKey
20 | }
21 | }
22 | }
23 | }
24 | }
25 | `).then((result) => {
26 | if (result.errors) {
27 | result.errors.forEach((e) => console.error(e.toString()))
28 | return Promise.reject(result.errors)
29 | }
30 |
31 | const posts = result.data.allMarkdownRemark.edges
32 |
33 | posts.forEach((edge) => {
34 | const id = edge.node.id
35 | createPage({
36 | path: edge.node.fields.slug,
37 | tags: edge.node.frontmatter.tags,
38 | component: path.resolve(
39 | `src/templates/${String(edge.node.frontmatter.templateKey)}.js`
40 | ),
41 | // additional data can be passed via context
42 | context: {
43 | id,
44 | },
45 | })
46 | })
47 |
48 | // Tag pages:
49 | let tags = []
50 | // Iterate through each post, putting all found tags into `tags`
51 | posts.forEach((edge) => {
52 | if (_.get(edge, `node.frontmatter.tags`)) {
53 | tags = tags.concat(edge.node.frontmatter.tags)
54 | }
55 | })
56 | // Eliminate duplicate tags
57 | tags = _.uniq(tags)
58 |
59 | // Make tag pages
60 | tags.forEach((tag) => {
61 | const tagPath = `/tags/${_.kebabCase(tag)}/`
62 |
63 | createPage({
64 | path: tagPath,
65 | component: path.resolve(`src/templates/tags.js`),
66 | context: {
67 | tag,
68 | },
69 | })
70 | })
71 | })
72 | }
73 |
74 | exports.onCreateNode = ({ node, actions, getNode }) => {
75 | const { createNodeField } = actions
76 |
77 | if (node.internal.type === `MarkdownRemark`) {
78 | const value = createFilePath({ node, getNode })
79 | createNodeField({
80 | name: `slug`,
81 | node,
82 | value,
83 | })
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/netlify.toml:
--------------------------------------------------------------------------------
1 | [build]
2 | publish = "public"
3 | command = "npm run build"
4 | [build.environment]
5 | NODE_VERSION = "14.15.0"
6 | YARN_VERSION = "1.22.4"
7 | YARN_FLAGS = "--no-ignore-optional"
8 |
9 |
10 |
--------------------------------------------------------------------------------
/netlify/functions/hello.js:
--------------------------------------------------------------------------------
1 | // For more info, check https://docs.netlify.com/functions/build-with-javascript
2 | module.exports.handler = async function(event, context) {
3 | console.log("queryStringParameters", event.queryStringParameters)
4 | return {
5 | // return null to show no errors
6 | statusCode: 200, // http status code
7 | body: JSON.stringify({
8 | msg: "Hello, World! This is better " + Math.round(Math.random() * 10)
9 | })
10 | }
11 | }
12 |
13 | // Now you are ready to access this API from anywhere in your Gatsby app! For example, in any event handler or lifecycle method, insert:
14 | // fetch("/.netlify/functions/hello")
15 | // .then(response => response.json())
16 | // .then(console.log)
17 | // For more info see: https://www.gatsbyjs.org/blog/2018-12-17-turning-the-static-dynamic/#static-dynamic-is-a-spectrum
18 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-starter-netlify-cms",
3 | "description": "Example Gatsby, and Netlify CMS project",
4 | "version": "1.1.3",
5 | "author": "Austin Green",
6 | "dependencies": {
7 | "bulma": "^0.9.0",
8 | "gatsby": "^4.0.0",
9 | "gatsby-plugin-image": "^2.0.0",
10 | "gatsby-plugin-netlify": "^5.0.0",
11 | "gatsby-plugin-netlify-cms": "^6.0.0",
12 | "gatsby-plugin-purgecss": "^6.0.0",
13 | "gatsby-plugin-react-helmet": "^5.0.0",
14 | "gatsby-plugin-sass": "^5.0.0",
15 | "gatsby-plugin-sharp": "^4.0.0",
16 | "gatsby-remark-copy-linked-files": "^5.0.0",
17 | "gatsby-remark-images": "^6.0.0",
18 | "gatsby-remark-relative-images": "^2.0.2",
19 | "gatsby-source-filesystem": "^4.0.0",
20 | "gatsby-transformer-remark": "^5.0.0",
21 | "gatsby-transformer-sharp": "^4.0.0",
22 | "lodash": "^4.17.15",
23 | "lodash-webpack-plugin": "^0.11.4",
24 | "netlify-cms-app": "^2.15.72",
25 | "netlify-cms-media-library-cloudinary": "^1.3.10",
26 | "netlify-cms-media-library-uploadcare": "^0.5.10",
27 | "prop-types": "^15.6.0",
28 | "react": "^17.0.0",
29 | "react-dom": "^17.0.0",
30 | "react-helmet": "^6.0.0",
31 | "sass": "^1.43.2",
32 | "uuid": "^8.0.0"
33 | },
34 | "keywords": [
35 | "gatsby"
36 | ],
37 | "license": "MIT",
38 | "main": "n/a",
39 | "scripts": {
40 | "clean": "gatsby clean",
41 | "start": "npm run develop",
42 | "build": "npm run clean && gatsby build",
43 | "develop": "npm run clean && gatsby develop",
44 | "serve": "gatsby serve",
45 | "format": "prettier --trailing-comma es5 --no-semi --single-quote --write \"{gatsby-*.js,src/**/*.js}\"",
46 | "test": "echo \"Error: no test specified\" && exit 1"
47 | },
48 | "devDependencies": {
49 | "prettier": "^2.0.5"
50 | },
51 | "engines": {
52 | "node": ">= 14.15.0"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["github>netlify/renovate-config:netlify-cms-starter"]
3 | }
4 |
--------------------------------------------------------------------------------
/src/cms/cms.js:
--------------------------------------------------------------------------------
1 | import CMS from 'netlify-cms-app'
2 | import uploadcare from 'netlify-cms-media-library-uploadcare'
3 | import cloudinary from 'netlify-cms-media-library-cloudinary'
4 |
5 | import AboutPagePreview from './preview-templates/AboutPagePreview'
6 | import BlogPostPreview from './preview-templates/BlogPostPreview'
7 | import ProductPagePreview from './preview-templates/ProductPagePreview'
8 | import IndexPagePreview from './preview-templates/IndexPagePreview'
9 |
10 | CMS.registerMediaLibrary(uploadcare)
11 | CMS.registerMediaLibrary(cloudinary)
12 |
13 | CMS.registerPreviewTemplate('index', IndexPagePreview)
14 | CMS.registerPreviewTemplate('about', AboutPagePreview)
15 | CMS.registerPreviewTemplate('products', ProductPagePreview)
16 | CMS.registerPreviewTemplate('blog', BlogPostPreview)
17 |
--------------------------------------------------------------------------------
/src/cms/preview-templates/AboutPagePreview.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { AboutPageTemplate } from '../../templates/about-page'
4 |
5 | const AboutPagePreview = ({ entry, widgetFor }) => (
6 |
10 | )
11 |
12 | AboutPagePreview.propTypes = {
13 | entry: PropTypes.shape({
14 | getIn: PropTypes.func,
15 | }),
16 | widgetFor: PropTypes.func,
17 | }
18 |
19 | export default AboutPagePreview
20 |
--------------------------------------------------------------------------------
/src/cms/preview-templates/BlogPostPreview.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { BlogPostTemplate } from '../../templates/blog-post'
4 |
5 | const BlogPostPreview = ({ entry, widgetFor }) => {
6 | const tags = entry.getIn(['data', 'tags'])
7 | return (
8 |
14 | )
15 | }
16 |
17 | BlogPostPreview.propTypes = {
18 | entry: PropTypes.shape({
19 | getIn: PropTypes.func,
20 | }),
21 | widgetFor: PropTypes.func,
22 | }
23 |
24 | export default BlogPostPreview
25 |
--------------------------------------------------------------------------------
/src/cms/preview-templates/IndexPagePreview.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { IndexPageTemplate } from '../../templates/index-page'
4 |
5 | const IndexPagePreview = ({ entry, getAsset }) => {
6 | const data = entry.getIn(['data']).toJS()
7 |
8 | if (data) {
9 | return (
10 |
19 | )
20 | } else {
21 | return
Loading...
22 | }
23 | }
24 |
25 | IndexPagePreview.propTypes = {
26 | entry: PropTypes.shape({
27 | getIn: PropTypes.func,
28 | }),
29 | getAsset: PropTypes.func,
30 | }
31 |
32 | export default IndexPagePreview
33 |
--------------------------------------------------------------------------------
/src/cms/preview-templates/ProductPagePreview.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { ProductPageTemplate } from '../../templates/product-page'
4 |
5 | const ProductPagePreview = ({ entry, getAsset }) => {
6 | const entryBlurbs = entry.getIn(['data', 'intro', 'blurbs'])
7 | const blurbs = entryBlurbs ? entryBlurbs.toJS() : []
8 |
9 | const entryTestimonials = entry.getIn(['data', 'testimonials'])
10 | const testimonials = entryTestimonials ? entryTestimonials.toJS() : []
11 |
12 | const entryPricingPlans = entry.getIn(['data', 'pricing', 'plans'])
13 | const pricingPlans = entryPricingPlans ? entryPricingPlans.toJS() : []
14 |
15 | return (
16 |
46 | )
47 | }
48 |
49 | ProductPagePreview.propTypes = {
50 | entry: PropTypes.shape({
51 | getIn: PropTypes.func,
52 | }),
53 | getAsset: PropTypes.func,
54 | }
55 |
56 | export default ProductPagePreview
57 |
--------------------------------------------------------------------------------
/src/components/BlogRoll.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 | import { Link, graphql, StaticQuery } from 'gatsby'
4 | import PreviewCompatibleImage from './PreviewCompatibleImage'
5 |
6 |
7 | const BlogRollTemplate = (props) => {
8 |
9 | const { edges: posts } = props.data.allMarkdownRemark;
10 |
11 | return (
12 |
13 | {posts &&
14 | posts.map(({ node: post }) => (
15 |
16 |
21 |
51 |
52 | {post.excerpt}
53 |
54 |
55 |
56 | Keep Reading →
57 |
58 |
59 |
60 |
61 | ))}
62 |
63 | )
64 | }
65 |
66 | BlogRoll.propTypes = {
67 | data: PropTypes.shape({
68 | allMarkdownRemark: PropTypes.shape({
69 | edges: PropTypes.array,
70 | }),
71 | }),
72 | }
73 |
74 |
75 | export default function BlogRoll() {
76 | return (
77 | }
113 | />
114 | );
115 | }
116 |
--------------------------------------------------------------------------------
/src/components/Content.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | export const HTMLContent = ({ content, className }) => (
5 |
6 | );
7 |
8 | const Content = ({ content, className }) => (
9 | {content}
10 | );
11 |
12 | Content.propTypes = {
13 | content: PropTypes.node,
14 | className: PropTypes.string,
15 | };
16 |
17 | HTMLContent.propTypes = Content.propTypes;
18 |
19 | export default Content;
20 |
--------------------------------------------------------------------------------
/src/components/Features.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import PropTypes from "prop-types";
3 | import PreviewCompatibleImage from "../components/PreviewCompatibleImage";
4 |
5 | const FeatureGrid = ({ gridItems }) => (
6 |
7 | {gridItems.map((item) => (
8 |
9 |
10 |
20 | {item.text}
21 |
22 |
23 | ))}
24 |
25 | );
26 |
27 | FeatureGrid.propTypes = {
28 | gridItems: PropTypes.arrayOf(
29 | PropTypes.shape({
30 | image: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
31 | text: PropTypes.string,
32 | })
33 | ),
34 | };
35 |
36 | export default FeatureGrid;
37 |
--------------------------------------------------------------------------------
/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Link } from "gatsby";
3 |
4 | import logo from "../img/logo.svg";
5 | import facebook from "../img/social/facebook.svg";
6 | import instagram from "../img/social/instagram.svg";
7 | import twitter from "../img/social/twitter.svg";
8 | import vimeo from "../img/social/vimeo.svg";
9 |
10 | const Footer = () => {
11 |
12 | return (
13 |
14 |
15 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 | Home
30 |
31 |
32 |
33 |
34 | About
35 |
36 |
37 |
38 |
39 | Products
40 |
41 |
42 |
43 |
44 | Form Examples
45 |
46 |
47 |
48 |
54 | Admin
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | Latest Stories
66 |
67 |
68 |
69 |
70 | Contact
71 |
72 |
73 |
74 |
75 |
76 |
107 |
108 |
109 |
110 |
111 | );
112 | };
113 |
114 | export default Footer;
115 |
--------------------------------------------------------------------------------
/src/components/FullWidthImage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { GatsbyImage } from "gatsby-plugin-image";
4 |
5 | export default function FullWidthImage(props) {
6 | const {
7 | height = 400,
8 | img,
9 | title,
10 | subheading,
11 | imgPosition = "top left",
12 | } = props;
13 |
14 | return (
15 |
16 |
23 | {img?.url ? (
24 |
37 | ) : (
38 |
54 | )}
55 | {(title || subheading) && (
56 |
66 | {/* Any content here will be centered in the component */}
67 | {title && (
68 |
79 | {title}
80 |
81 | )}
82 | {subheading && (
83 |
95 | {subheading}
96 |
97 | )}
98 |
99 | )}
100 |
101 |
102 | );
103 | }
104 |
105 | FullWidthImage.propTypes = {
106 | img: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
107 | title: PropTypes.string,
108 | height: PropTypes.number,
109 | subheading: PropTypes.string,
110 | };
111 |
--------------------------------------------------------------------------------
/src/components/Layout.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Helmet } from "react-helmet";
3 | import Footer from "../components/Footer";
4 | import Navbar from "../components/Navbar";
5 | import "./all.sass";
6 | import useSiteMetadata from "./SiteMetadata";
7 | import { withPrefix } from "gatsby";
8 |
9 | const TemplateWrapper = ({ children }) => {
10 | const { title, description } = useSiteMetadata();
11 | return (
12 |
13 |
14 |
15 |
{title}
16 |
17 |
18 |
23 |
29 |
35 |
36 |
41 |
42 |
43 |
44 |
45 |
46 |
50 |
51 |
52 |
{children}
53 |
54 |
55 | );
56 | };
57 |
58 | export default TemplateWrapper;
59 |
--------------------------------------------------------------------------------
/src/components/Navbar.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link } from "gatsby";
3 | import github from "../img/github-icon.svg";
4 | import logo from "../img/logo.svg";
5 |
6 | const Navbar = () => {
7 | const [isActive, setIsActive] = useState(false);
8 |
9 | return (
10 |
15 |
16 |
17 |
18 |
19 |
20 | {/* Hamburger menu */}
21 |
setIsActive(!isActive)}
25 | >
26 |
27 |
28 |
29 |
30 |
31 |
74 |
75 |
76 | );
77 | };
78 |
79 | export default Navbar;
80 |
--------------------------------------------------------------------------------
/src/components/PreviewCompatibleImage.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import PropTypes from "prop-types";
3 | import { GatsbyImage } from "gatsby-plugin-image";
4 |
5 | const PreviewCompatibleImage = ({ imageInfo }) => {
6 | const imageStyle = { borderRadius: "5px" };
7 |
8 | const { alt = "", childImageSharp, image } = imageInfo;
9 |
10 | if (!!image && !!image.childImageSharp) {
11 | return (
12 |
17 | );
18 | } else if (!!childImageSharp) {
19 | return (
20 |
25 | );
26 | // for Netlify CMS
27 | } else if (image) {
28 | return ;
29 | } else {
30 | return null
31 | }
32 | };
33 |
34 | PreviewCompatibleImage.propTypes = {
35 | imageInfo: PropTypes.shape({
36 | alt: PropTypes.string,
37 | childImageSharp: PropTypes.object,
38 | image: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired,
39 | style: PropTypes.object,
40 | }).isRequired,
41 | };
42 |
43 | export default PreviewCompatibleImage;
44 |
--------------------------------------------------------------------------------
/src/components/Pricing.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import PropTypes from "prop-types";
3 |
4 | const Pricing = ({ data }) => (
5 |
6 | {data.map((price) => (
7 |
8 |
9 |
10 | {price.plan}
11 |
12 |
13 | ${price.price}
14 |
15 | {price.description}
16 |
17 | {price.items.map((item) => (
18 |
19 | {item}
20 |
21 | ))}
22 |
23 |
24 |
25 | ))}
26 |
27 | );
28 |
29 | Pricing.propTypes = {
30 | data: PropTypes.arrayOf(
31 | PropTypes.shape({
32 | plan: PropTypes.string,
33 | price: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
34 | description: PropTypes.string,
35 | items: PropTypes.array,
36 | })
37 | ),
38 | };
39 |
40 | export default Pricing;
41 |
--------------------------------------------------------------------------------
/src/components/SiteMetadata.js:
--------------------------------------------------------------------------------
1 | import { graphql, useStaticQuery } from 'gatsby'
2 |
3 | const useSiteMetadata = () => {
4 | const { site } = useStaticQuery(
5 | graphql`
6 | query SITE_METADATA_QUERY {
7 | site {
8 | siteMetadata {
9 | title
10 | description
11 | }
12 | }
13 | }
14 | `
15 | )
16 | return site.siteMetadata
17 | }
18 |
19 | export default useSiteMetadata
20 |
--------------------------------------------------------------------------------
/src/components/Testimonials.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import PropTypes from "prop-types";
3 | import { v4 } from "uuid";
4 |
5 | const Testimonials = ({ testimonials }) => (
6 |
7 | {testimonials.map((testimonial) => (
8 |
9 |
10 | {testimonial.quote}
11 |
12 | – {testimonial.author}
13 |
14 |
15 | ))}
16 |
17 | );
18 |
19 | Testimonials.propTypes = {
20 | testimonials: PropTypes.arrayOf(
21 | PropTypes.shape({
22 | quote: PropTypes.string,
23 | author: PropTypes.string,
24 | })
25 | ),
26 | };
27 |
28 | export default Testimonials;
29 |
--------------------------------------------------------------------------------
/src/components/all.sass:
--------------------------------------------------------------------------------
1 | @import "~bulma/sass/utilities/initial-variables"
2 |
3 | // Config vars
4 | // Kaldi logo brand color: #FD461E
5 | $kaldi-red: #D64000
6 | $kaldi-red-invert: #fff
7 |
8 | $primary: $kaldi-red
9 | $primary-invert: $kaldi-red-invert
10 | $body-color: #333
11 | $black: #2b2523
12 |
13 | .navbar .navbar-menu
14 | box-shadow: none !important
15 |
16 | .content .taglist
17 | list-style: none
18 | margin-bottom: 0
19 | margin-left: 0
20 | margin-right: 1.5rem
21 | margin-top: 1.5rem
22 | display: flex
23 | flex-wrap: wrap
24 | justify-content: left
25 | align-items: center
26 | li
27 | padding: 0 2rem 1rem 0
28 | margin-bottom: 1.5rem
29 | margin-top: 0
30 |
31 | // Helper Classes
32 | .full-width-image-container
33 | width: 100vw
34 | height: 400px
35 | position: relative
36 | left: 50%
37 | right: 50%
38 | margin: 2em -50vw
39 | background-size: cover
40 | background-position: bottom
41 | display: flex
42 | justify-content: center
43 | align-items: center
44 |
45 | .full-width-image
46 | width: 100vw
47 | height: 400px
48 | background-size: cover
49 | background-position: bottom
50 | display: flex
51 | justify-content: center
52 | align-items: center
53 |
54 | .btn
55 | display: inline-block
56 | padding: 12px 16px 10px
57 | font-size: 18px
58 | font-size: 1rem
59 | line-height: 1.25
60 | background-color: #fff
61 | border-radius: .25rem
62 | text-decoration: none
63 | font-weight: 700
64 | color: #CC3700
65 | text-align: center
66 | -webkit-box-shadow: inset 0 0 0 2px #CC3700
67 | box-shadow: inset 0 0 0 2px #f40
68 | -webkit-transition: all .15s ease
69 | transition: all .15s ease
70 |
71 | .margin-top-0
72 | margin-top: 0 !important
73 |
74 | .navbar-item .icon
75 | color: $primary
76 | // Override for use of svg's from https://simpleicons.org/
77 | .icon svg
78 | width: 1.5rem
79 | height: 1.5rem
80 | fill: currentColor
81 | .navbar-brand .navbar-item.logo
82 | padding: 0 1rem
83 | footer.footer
84 | padding: 3rem 0rem 0rem
85 | background-color: transparent
86 |
87 | //Menu overrides
88 | $menu-item-color: $white-ter
89 | $menu-item-hover-color: $black !important
90 | $menu-item-hover-background-color: $black
91 | $menu-item-active-color: $kaldi-red-invert
92 | $menu-item-active-background-color: $kaldi-red-invert
93 | $menu-list-border-left: 1px solid $kaldi-red-invert
94 | $menu-label-color: $white-ter
95 |
96 |
97 | .menu-label
98 | font-size: 1em !important
99 | text-align: left
100 | .menu-list
101 | list-style: none !important
102 | text-align: left
103 | .social
104 | padding: 2em
105 | .social a
106 | padding: .5em .5em .3em .5em
107 | border-radius: 1em
108 | background-color: $white-ter
109 | margin: .5em
110 | width: 1em
111 | height: 1em
112 | vertical-align: middle
113 | display: inline
114 |
115 | // blog roll
116 | .blog-list-item.is-featured
117 | background-color: #d6400033
118 | .blog-list-item header
119 | display: flex
120 | margin-bottom: 1em
121 | .blog-list-item .featured-thumbnail
122 | flex-basis: 35%
123 | margin: 0 1.5em 0 0
124 |
125 |
126 | @import "~bulma"
127 |
128 |
129 | // responsiveness
130 | +tablet-only
131 | .blog-list-item .featured-thumbnail
132 | flex-basis: 50%
133 |
134 | +mobile
135 | .blog-list-item header
136 | display: block
137 | .blog-list-item .featured-thumbnail
138 | text-align: center
139 | max-width: 70%
140 | margin: 0 0 1em
--------------------------------------------------------------------------------
/src/img/github-icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/src/img/logo.svg:
--------------------------------------------------------------------------------
1 | Logo
2 |
--------------------------------------------------------------------------------
/src/img/social/facebook.svg:
--------------------------------------------------------------------------------
1 | Facebook icon
--------------------------------------------------------------------------------
/src/img/social/instagram.svg:
--------------------------------------------------------------------------------
1 | Instagram icon
--------------------------------------------------------------------------------
/src/img/social/twitter.svg:
--------------------------------------------------------------------------------
1 | Twitter icon
--------------------------------------------------------------------------------
/src/img/social/vimeo.svg:
--------------------------------------------------------------------------------
1 | Vimeo icon
--------------------------------------------------------------------------------
/src/pages/404.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Layout from "../components/Layout";
3 |
4 | const NotFoundPage = () => (
5 |
6 |
7 |
NOT FOUND
8 |
You just hit a route that doesn't exist... the sadness.
9 |
10 |
11 | );
12 |
13 | export default NotFoundPage;
14 |
--------------------------------------------------------------------------------
/src/pages/about/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | templateKey: 'about-page'
3 | path: /about
4 | title: About our values
5 | ---
6 | ### Shade-grown coffee
7 | Coffee is a small tree or shrub that grows in the forest understory in its wild form, and traditionally was grown commercially under other trees that provided shade. The forest-like structure of shade coffee farms provides habitat for a great number of migratory and resident species.
8 |
9 | ### Single origin
10 | Single-origin coffee is coffee grown within a single known geographic origin. Sometimes, this is a single farm or a specific collection of beans from a single country. The name of the coffee is then usually the place it was grown to whatever degree available.
11 |
12 | ### Sustainable farming
13 | Sustainable agriculture is farming in sustainable ways based on an understanding of ecosystem services, the study of relationships between organisms and their environment. What grows where and how it is grown are a matter of choice and careful consideration for nature and communities.
14 |
15 | ### Direct sourcing
16 | Direct trade is a form of sourcing practiced by some coffee roasters. Advocates of direct trade practices promote direct communication and price negotiation between buyer and farmer, along with systems that encourage and incentivize quality.
17 |
18 | ### Reinvest profits
19 | We want to truly empower the communities that bring amazing coffee to you. That’s why we reinvest 20% of our profits into farms, local businesses and schools everywhere our coffee is grown. You can see the communities grow and learn more about coffee farming on our blog.
20 |
--------------------------------------------------------------------------------
/src/pages/blog/2016-12-17-making-sense-of-the-scaas-new-flavor-wheel.md:
--------------------------------------------------------------------------------
1 | ---
2 | templateKey: blog-post
3 | title: Making sense of the SCAA’s new Flavor Wheel
4 | date: 2016-12-17T15:04:10.000Z
5 | featuredpost: false
6 | featuredimage: /img/flavor_wheel.jpg
7 | description: The Coffee Taster’s Flavor Wheel, the official resource used by coffee tasters, has been revised for the first time this year.
8 | tags:
9 | - flavor
10 | - tasting
11 | ---
12 | 
13 |
14 | The SCAA updated the wheel to reflect the finer nuances needed to describe flavors more precisely. The new descriptions are more detailed and hence allow cuppers to distinguish between more flavors.
15 |
16 | While this is going to be a big change for professional coffee tasters, it means a lot to you as a consumer as well. We’ll explain how the wheel came to be, how pros use it and what the flavors actually mean.
17 |
18 | ## What the updates mean to you
19 |
20 | The Specialty Coffee Association of America (SCAA), founded in 1982, is a non-profit trade organization for the specialty coffee industry. With members located in more than 40 countries, SCAA represents every segment of the specialty coffee industry, including:
21 |
22 | * producers
23 | * roasters
24 | * importers/exporters
25 | * retailers
26 | * manufacturers
27 | * baristas
28 |
29 | For over 30 years, SCAA has been dedicated to creating a vibrant specialty coffee community by recognizing, developing and promoting specialty coffee. SCAA sets and maintains quality standards for the industry, conducts market research, and provides education, training, resources, and business services for its members.
30 |
31 | Coffee cupping, or coffee tasting, is the practice of observing the tastes and aromas of brewed coffee. It is a professional practice but can be done informally by anyone or by professionals known as "Q Graders". A standard coffee cupping procedure involves deeply sniffing the coffee, then loudly slurping the coffee so it spreads to the back of the tongue.
32 |
33 | The coffee taster attempts to measure aspects of the coffee's taste, specifically the body (the texture or mouthfeel, such as oiliness), sweetness, acidity (a sharp and tangy feeling, like when biting into an orange), flavour (the characters in the cup), and aftertaste. Since coffee beans embody telltale flavours from the region where they were grown, cuppers may attempt to identify the coffee's origin.
34 |
--------------------------------------------------------------------------------
/src/pages/blog/2017-01-04-a-beginners-guide-to-brewing-with-chemex.md:
--------------------------------------------------------------------------------
1 | ---
2 | templateKey: blog-post
3 | title: A beginners’ guide to brewing with Chemex
4 | date: 2017-01-04T15:04:10.000Z
5 | featuredpost: false
6 | featuredimage: /img/chemex.jpg
7 | description: Brewing with a Chemex probably seems like a complicated, time-consuming ordeal, but once you get used to the process, it becomes a soothing ritual that's worth the effort every time.
8 | tags:
9 | - brewing
10 | - chemex
11 | ---
12 | 
13 |
14 | This week we’ll **take** a look at all the steps required to make astonishing coffee with a Chemex at home. The Chemex Coffeemaker is a manual, pour-over style glass-container coffeemaker that Peter Schlumbohm invented in 1941, and which continues to be manufactured by the Chemex Corporation in Chicopee, Massachusetts.
15 |
16 | In 1958, designers at the [Illinois Institute of Technology](https://www.spacefarm.digital) said that the Chemex Coffeemaker is _"one of the best-designed products of modern times"_, and so is included in the collection of the Museum of Modern Art in New York City.
17 |
18 | ## The little secrets of Chemex brewing
19 |
20 | The Chemex Coffeemaker consists of an hourglass-shaped glass flask with a conical funnel-like neck (rather than the cylindrical neck of an Erlenmeyer flask) and uses proprietary filters, made of bonded paper (thicker-gauge paper than the standard paper filters for a drip-method coffeemaker) that removes most of the coffee oils, brewing coffee with a taste that is different than coffee brewed in other coffee-making systems; also, the thicker paper of the Chemex coffee filters may assist in removing cafestol, a cholesterol-containing compound found in coffee oils. Here’s three important tips newbies forget about:
21 |
22 | 1. Always buy dedicated Chemex filters.
23 | 2. Use a scale, don’t try to eyeball it.
24 | 3. Never skip preheating the glass.
25 | 4. Timing is key, don’t forget the clock.
26 |
27 | The most visually distinctive feature of the Chemex is the heatproof wooden collar around the neck, allowing it to be handled and poured when full of hot water. This is turned, then split in two to allow it to fit around the glass neck. The two pieces are held loosely in place by a tied leather thong. The pieces are not tied tightly and can still move slightly, retained by the shape of the conical glass.
28 |
29 | For a design piece that became popular post-war at a time of Modernism and precision manufacture, this juxtaposition of natural wood and the organic nature of a hand-tied knot with the laboratory nature of glassware was a distinctive feature of its appearance.
30 |
--------------------------------------------------------------------------------
/src/pages/blog/2017-01-04-just-in-small-batch-of-jamaican-blue-mountain-in-store-next-week.md:
--------------------------------------------------------------------------------
1 | ---
2 | templateKey: 'blog-post'
3 | title: 'Just in: small batch of Jamaican Blue Mountain in store next week'
4 | date: 2017-01-04T15:04:10.000Z
5 | featuredpost: true
6 | description: >-
7 | We’re proud to announce that we’ll be offering a small batch of Jamaica Blue
8 | Mountain coffee beans in our store next week.
9 | tags:
10 | - jamaica
11 | - green beans
12 | - flavor
13 | - tasting
14 | ---
15 |
16 | We expect the shipment of a limited quantity of green beans next Monday. We’ll be offering the roasted beans from Tuesday, but quantities are limited, so be quick.
17 |
18 | Blue Mountain Peak is the highest mountain in Jamaica and one of the highest peaks in the Caribbean at 7,402 ft. It is the home of Blue Mountain coffee and their famous tours. It is located on the border of the Portland and Saint Thomas parishes of Jamaica.
19 |
20 | ## A little history
21 |
22 | The Blue Mountains are considered by many to be a hiker's and camper's paradise. The traditional Blue Mountain trek is a 7-mile hike to the peak and consists of a 3,000-foot increase in elevation. Jamaicans prefer to reach the peak at sunrise, thus the 3–4 hour hike is usually undertaken in darkness. Since the sky is usually very clear in the mornings, Cuba can be seen in the distance.
23 |
24 | >Some of the plants found on the Blue Mountain cannot be found anywhere else in the world and they are often of a dwarfed sort.
25 |
26 | This is mainly due to the cold climate which inhibits growth. The small coffee farming communities of Claverty Cottage and Hagley Gap are located near the peak.
27 |
28 | ## What you need to know before trying
29 |
30 | Jamaican Blue Mountain Coffee or Jamaica Blue Mountain Coffee is a classification of coffee grown in the Blue Mountains of Jamaica. The best lots of Blue Mountain coffee are noted for their mild flavor and lack of bitterness. Over the past few decades, this coffee has developed a reputation that has made it one of the most expensive and sought-after coffees in the world. Over 80% of all Jamaican Blue Mountain Coffee is exported to Japan. In addition to its use for brewed coffee, the beans are the flavor base of Tia Maria coffee liqueur.
31 |
32 | Jamaican Blue Mountain Coffee is a globally protected certification mark, meaning only coffee certified by the Coffee Industry Board of Jamaica can be labeled as such. It comes from a recognized growing region in the Blue Mountain region of Jamaica, and its cultivation is monitored by the Coffee Industry Board of Jamaica.
33 |
34 | The Blue Mountains are generally located between Kingston to the south and Port Antonio to the north. Rising 7,402 ft, they are some of the highest mountains in the Caribbean. The climate of the region is cool and misty with high rainfall. The soil is rich, with excellent drainage. This combination of climate and soil is considered ideal for coffee.
35 |
--------------------------------------------------------------------------------
/src/pages/blog/index.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 |
3 | import Layout from "../../components/Layout";
4 | import BlogRoll from "../../components/BlogRoll";
5 |
6 | export default class BlogIndexPage extends React.Component {
7 | render() {
8 | return (
9 |
10 |
16 |
25 | Latest Stories
26 |
27 |
28 |
35 |
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/pages/contact/examples.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import Link from "gatsby-link";
3 | import Layout from "../../components/Layout";
4 |
5 | export default class Index extends React.Component {
6 | render() {
7 | return (
8 |
9 |
10 |
11 |
12 |
Hi people
13 |
14 | This is an example site integrating Netlify’s form handling with
15 | Gatsby
16 |
17 |
18 |
19 | Basic contact form
20 |
21 |
22 | Form with file upload
23 |
24 |
25 |
26 |
Troubleshooting
27 |
Forms stop working after upgrading to Gatsby v2
28 |
29 | This can be caused by the offline-plugin.{" "}
30 |
31 | Workaround
32 | {" "}
33 | is to use ?no-cache=1
in the POST url to prevent
34 | the service worker from handling form submissions
35 |
36 |
Adding reCAPTCHA
37 |
38 | If you are planning to add reCAPTCHA please go to{" "}
39 |
40 | imorente/gatsby-netlify-form-example
41 | {" "}
42 | for a working example.
43 |
44 |
45 |
46 |
47 |
48 | );
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/pages/contact/file-upload.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { navigate } from "gatsby-link";
3 | import Layout from "../../components/Layout";
4 |
5 | function encode(data) {
6 | const formData = new FormData();
7 |
8 | for (const key of Object.keys(data)) {
9 | formData.append(key, data[key]);
10 | }
11 |
12 | return formData;
13 | }
14 |
15 | export default class Contact extends React.Component {
16 | constructor(props) {
17 | super(props);
18 | this.state = {};
19 | }
20 |
21 | handleChange = (e) => {
22 | this.setState({ [e.target.name]: e.target.value });
23 | };
24 |
25 | handleAttachment = (e) => {
26 | this.setState({ [e.target.name]: e.target.files[0] });
27 | };
28 |
29 | handleSubmit = (e) => {
30 | e.preventDefault();
31 | const form = e.target;
32 | fetch("/", {
33 | method: "POST",
34 | body: encode({
35 | "form-name": form.getAttribute("name"),
36 | ...this.state,
37 | }),
38 | })
39 | .then(() => navigate(form.getAttribute("action")))
40 | .catch((error) => alert(error));
41 | };
42 |
43 | render() {
44 | return (
45 |
46 |
47 |
48 |
49 |
File Upload
50 |
102 |
103 |
104 |
105 |
106 | );
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/pages/contact/index.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { navigate } from "gatsby-link";
3 | import Layout from "../../components/Layout";
4 |
5 | function encode(data) {
6 | return Object.keys(data)
7 | .map((key) => encodeURIComponent(key) + "=" + encodeURIComponent(data[key]))
8 | .join("&");
9 | }
10 |
11 | export default class Index extends React.Component {
12 | constructor(props) {
13 | super(props);
14 | this.state = { isValidated: false };
15 | }
16 |
17 | handleChange = (e) => {
18 | this.setState({ [e.target.name]: e.target.value });
19 | };
20 |
21 | handleSubmit = (e) => {
22 | e.preventDefault();
23 | const form = e.target;
24 | fetch("/", {
25 | method: "POST",
26 | headers: { "Content-Type": "application/x-www-form-urlencoded" },
27 | body: encode({
28 | "form-name": form.getAttribute("name"),
29 | ...this.state,
30 | }),
31 | })
32 | .then(() => navigate(form.getAttribute("action")))
33 | .catch((error) => alert(error));
34 | };
35 |
36 | render() {
37 | return (
38 |
39 |
112 |
113 | );
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/pages/contact/thanks.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Layout from "../../components/Layout";
3 |
4 | // eslint-disable-next-line
5 | export default () => (
6 |
7 |
8 |
9 |
10 |
Thank you!
11 |
This is a custom thank you page for form submissions
12 |
13 |
14 |
15 |
16 | );
17 |
--------------------------------------------------------------------------------
/src/pages/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | templateKey: index-page
3 | title: Great coffee with a conscience
4 | image: /img/home-jumbotron.jpg
5 | heading: Great coffee with a conscience
6 | subheading: Support sustainable farming while enjoying a cup
7 | mainpitch:
8 | title: Why Kaldi
9 | description: >
10 | Kaldi is the coffee store for everyone who believes that great coffee
11 | shouldn't just taste good, it should do good too. We source all of our beans
12 | directly from small scale sustainable farmers and make sure part of the
13 | profits are reinvested in their communities.
14 | description: >-
15 | Kaldi is the ultimate spot for coffee lovers who want to learn about their
16 | java’s origin and support the farmers that grew it. We take coffee production,
17 | roasting and brewing seriously and we’re glad to pass that knowledge to
18 | anyone.
19 | intro:
20 | blurbs:
21 | - image: /img/coffee.png
22 | text: >
23 | We sell green and roasted coffee beans that are sourced directly from
24 | independent farmers and farm cooperatives. We’re proud to offer a
25 | variety of coffee beans grown with great care for the environment and
26 | local communities. Check our post or contact us directly for current
27 | availability.
28 | - image: /img/coffee-gear.png
29 | text: >
30 | We offer a small, but carefully curated selection of brewing gear and
31 | tools for every taste and experience level. No matter if you roast your
32 | own beans or just bought your first french press, you’ll find a gadget
33 | to fall in love with in our shop.
34 | - image: /img/tutorials.png
35 | text: >
36 | Love a great cup of coffee, but never knew how to make one? Bought a
37 | fancy new Chemex but have no clue how to use it? Don't worry, we’re here
38 | to help. You can schedule a custom 1-on-1 consultation with our baristas
39 | to learn anything you want to know about coffee roasting and brewing.
40 | Email us or call the store for details.
41 | - image: /img/meeting-space.png
42 | text: >
43 | We believe that good coffee has the power to bring people together.
44 | That’s why we decided to turn a corner of our shop into a cozy meeting
45 | space where you can hang out with fellow coffee lovers and learn about
46 | coffee making techniques. All of the artwork on display there is for
47 | sale. The full price you pay goes to the artist.
48 | heading: What we offer
49 | description: >
50 | Kaldi is the ultimate spot for coffee lovers who want to learn about their
51 | java’s origin and support the farmers that grew it. We take coffee
52 | production, roasting and brewing seriously and we’re glad to pass that
53 | knowledge to anyone. This is an edit via identity...
54 | main:
55 | heading: Great coffee with no compromises
56 | description: >
57 | We hold our coffee to the highest standards from the shrub to the cup.
58 | That’s why we’re meticulous and transparent about each step of the coffee’s
59 | journey. We personally visit each farm to make sure the conditions are
60 | optimal for the plants, farmers and the local environment.
61 | image1:
62 | alt: A close-up of a paper filter filled with ground coffee
63 | image: /img/products-grid3.jpg
64 | image2:
65 | alt: A green cup of a coffee on a wooden table
66 | image: /img/products-grid2.jpg
67 | image3:
68 | alt: Coffee beans
69 | image: /img/products-grid1.jpg
70 | ---
71 |
--------------------------------------------------------------------------------
/src/pages/products/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | templateKey: 'product-page'
3 | path: /products
4 | title: Our Coffee
5 | image: /img/jumbotron.jpg
6 | heading: Great coffee with a conscience
7 | description: >-
8 | Kaldi is the ultimate spot for coffee lovers who want to learn about their
9 | java’s origin and support the farmers that grew it. We take coffee production,
10 | roasting and brewing seriously and we’re glad to pass that knowledge to
11 | anyone.
12 | intro:
13 | blurbs:
14 | - image: /img/coffee.png
15 | text: >
16 | We sell green and roasted coffee beans that are sourced directly from
17 | independent farmers and farm cooperatives. We’re proud to offer a
18 | variety of coffee beans grown with great care for the environment and
19 | local communities. Check our post or contact us directly for current
20 | availability.
21 | - image: /img/coffee-gear.png
22 | text: >
23 | We offer a small, but carefully curated selection of brewing gear and
24 | tools for every taste and experience level. No matter if you roast your
25 | own beans or just bought your first french press, you’ll find a gadget
26 | to fall in love with in our shop.
27 | - image: /img/tutorials.png
28 | text: >
29 | Love a great cup of coffee, but never knew how to make one? Bought a
30 | fancy new Chemex but have no clue how to use it? Don't worry, we’re here
31 | to help. You can schedule a custom 1-on-1 consultation with our baristas
32 | to learn anything you want to know about coffee roasting and brewing.
33 | Email us or call the store for details.
34 | - image: /img/meeting-space.png
35 | text: >
36 | We believe that good coffee has the power to bring people together.
37 | That’s why we decided to turn a corner of our shop into a cozy meeting
38 | space where you can hang out with fellow coffee lovers and learn about
39 | coffee making techniques. All of the artwork on display there is for
40 | sale. The full price you pay goes to the artist.
41 | heading: What we offer
42 | description: >
43 | Kaldi is the ultimate spot for coffee lovers who want to learn about their
44 | java’s origin and support the farmers that grew it. We take coffee
45 | production, roasting and brewing seriously and we’re glad to pass that
46 | knowledge to anyone. This is an edit via identity...
47 | main:
48 | heading: Great coffee with no compromises
49 | description: >
50 | We hold our coffee to the highest standards from the shrub to the cup.
51 | That’s why we’re meticulous and transparent about each step of the coffee’s
52 | journey. We personally visit each farm to make sure the conditions are
53 | optimal for the plants, farmers and the local environment.
54 | image1:
55 | alt: A close-up of a paper filter filled with ground coffee
56 | image: /img/products-grid3.jpg
57 | image2:
58 | alt: A green cup of a coffee on a wooden table
59 | image: /img/products-grid2.jpg
60 | image3:
61 | alt: Coffee beans
62 | image: /img/products-grid1.jpg
63 | testimonials:
64 | - author: Elisabeth Kaurismäki
65 | quote: >-
66 | The first time I tried Kaldi’s coffee, I couldn’t even believe that was
67 | the same thing I’ve been drinking every morning.
68 | - author: Philipp Trommler
69 | quote: >-
70 | Kaldi is the place to go if you want the best quality coffee. I love their
71 | stance on empowering farmers and transparency.
72 | full_image: /img/products-full-width.jpg
73 | pricing:
74 | heading: Monthly subscriptions
75 | description: >-
76 | We make it easy to make great coffee a part of your life. Choose one of our
77 | monthly subscription plans to receive great coffee at your doorstep each
78 | month. Contact us about more details and payment info.
79 | plans:
80 | - description: Perfect for the drinker who likes to enjoy 1-2 cups per day.
81 | items:
82 | - 3 lbs of coffee per month
83 | - Green or roasted beans"
84 | - One or two varieties of beans"
85 | plan: Small
86 | price: '50'
87 | - description: 'Great for avid drinkers, java-loving couples and bigger crowds'
88 | items:
89 | - 6 lbs of coffee per month
90 | - Green or roasted beans
91 | - Up to 4 different varieties of beans
92 | plan: Big
93 | price: '80'
94 | - description: Want a few tiny batches from different varieties? Try our custom plan
95 | items:
96 | - Whatever you need
97 | - Green or roasted beans
98 | - Unlimited varieties
99 | plan: Custom
100 | price: '??'
101 | ---
102 |
--------------------------------------------------------------------------------
/src/pages/tags/index.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { kebabCase } from "lodash";
3 | import { Helmet } from "react-helmet";
4 | import { Link, graphql } from "gatsby";
5 | import Layout from "../../components/Layout";
6 |
7 | const TagsPage = ({
8 | data: {
9 | allMarkdownRemark: { group },
10 | site: {
11 | siteMetadata: { title },
12 | },
13 | },
14 | }) => (
15 |
16 |
17 |
18 |
19 |
20 |
24 |
Tags
25 |
26 | {group.map((tag) => (
27 |
28 |
29 | {tag.fieldValue} ({tag.totalCount})
30 |
31 |
32 | ))}
33 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 |
41 | export default TagsPage;
42 |
43 | export const tagPageQuery = graphql`
44 | query TagsQuery {
45 | site {
46 | siteMetadata {
47 | title
48 | }
49 | }
50 | allMarkdownRemark(limit: 1000) {
51 | group(field: frontmatter___tags) {
52 | fieldValue
53 | totalCount
54 | }
55 | }
56 | }
57 | `;
58 |
--------------------------------------------------------------------------------
/src/templates/about-page.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { graphql } from "gatsby";
4 | import Layout from "../components/Layout";
5 | import Content, { HTMLContent } from "../components/Content";
6 |
7 | // eslint-disable-next-line
8 | export const AboutPageTemplate = ({ title, content, contentComponent }) => {
9 | const PageContent = contentComponent || Content;
10 |
11 | return (
12 |
13 |
14 |
15 |
16 |
17 |
18 | {title}
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 | };
28 |
29 | AboutPageTemplate.propTypes = {
30 | title: PropTypes.string.isRequired,
31 | content: PropTypes.string,
32 | contentComponent: PropTypes.func,
33 | };
34 |
35 | const AboutPage = ({ data }) => {
36 | const { markdownRemark: post } = data;
37 |
38 | return (
39 |
40 |
45 |
46 | );
47 | };
48 |
49 | AboutPage.propTypes = {
50 | data: PropTypes.object.isRequired,
51 | };
52 |
53 | export default AboutPage;
54 |
55 | export const aboutPageQuery = graphql`
56 | query AboutPage($id: String!) {
57 | markdownRemark(id: { eq: $id }) {
58 | html
59 | frontmatter {
60 | title
61 | }
62 | }
63 | }
64 | `;
65 |
--------------------------------------------------------------------------------
/src/templates/blog-post.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { kebabCase } from "lodash";
4 | import { Helmet } from "react-helmet";
5 | import { graphql, Link } from "gatsby";
6 | import Layout from "../components/Layout";
7 | import Content, { HTMLContent } from "../components/Content";
8 |
9 | // eslint-disable-next-line
10 | export const BlogPostTemplate = ({
11 | content,
12 | contentComponent,
13 | description,
14 | tags,
15 | title,
16 | helmet,
17 | }) => {
18 | const PostContent = contentComponent || Content;
19 |
20 | return (
21 |
22 | {helmet || ""}
23 |
24 |
25 |
26 |
27 | {title}
28 |
29 |
{description}
30 |
31 | {tags && tags.length ? (
32 |
33 |
Tags
34 |
35 | {tags.map((tag) => (
36 |
37 | {tag}
38 |
39 | ))}
40 |
41 |
42 | ) : null}
43 |
44 |
45 |
46 |
47 | );
48 | };
49 |
50 | BlogPostTemplate.propTypes = {
51 | content: PropTypes.node.isRequired,
52 | contentComponent: PropTypes.func,
53 | description: PropTypes.string,
54 | title: PropTypes.string,
55 | helmet: PropTypes.object,
56 | };
57 |
58 | const BlogPost = ({ data }) => {
59 | const { markdownRemark: post } = data;
60 |
61 | return (
62 |
63 |
69 | {`${post.frontmatter.title}`}
70 |
74 |
75 | }
76 | tags={post.frontmatter.tags}
77 | title={post.frontmatter.title}
78 | />
79 |
80 | );
81 | };
82 |
83 | BlogPost.propTypes = {
84 | data: PropTypes.shape({
85 | markdownRemark: PropTypes.object,
86 | }),
87 | };
88 |
89 | export default BlogPost;
90 |
91 | export const pageQuery = graphql`
92 | query BlogPostByID($id: String!) {
93 | markdownRemark(id: { eq: $id }) {
94 | id
95 | html
96 | frontmatter {
97 | date(formatString: "MMMM DD, YYYY")
98 | title
99 | description
100 | tags
101 | }
102 | }
103 | }
104 | `;
105 |
--------------------------------------------------------------------------------
/src/templates/index-page.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { Link, graphql } from "gatsby";
4 | import { getImage } from "gatsby-plugin-image";
5 |
6 | import Layout from "../components/Layout";
7 | import Features from "../components/Features";
8 | import BlogRoll from "../components/BlogRoll";
9 | import FullWidthImage from "../components/FullWidthImage";
10 |
11 | // eslint-disable-next-line
12 | export const IndexPageTemplate = ({
13 | image,
14 | title,
15 | heading,
16 | subheading,
17 | mainpitch,
18 | description,
19 | intro,
20 | }) => {
21 | const heroImage = getImage(image) || image;
22 |
23 | return (
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
{mainpitch.title}
35 |
36 |
37 |
{mainpitch.description}
38 |
39 |
40 |
41 |
42 |
43 | {heading}
44 |
45 |
{description}
46 |
47 |
48 |
49 |
50 |
51 |
52 | See all products
53 |
54 |
55 |
56 |
57 |
58 | Latest stories
59 |
60 |
61 |
62 |
63 | Read more
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | );
75 | };
76 |
77 | IndexPageTemplate.propTypes = {
78 | image: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
79 | title: PropTypes.string,
80 | heading: PropTypes.string,
81 | subheading: PropTypes.string,
82 | mainpitch: PropTypes.object,
83 | description: PropTypes.string,
84 | intro: PropTypes.shape({
85 | blurbs: PropTypes.array,
86 | }),
87 | };
88 |
89 | const IndexPage = ({ data }) => {
90 | const { frontmatter } = data.markdownRemark;
91 |
92 | return (
93 |
94 |
103 |
104 | );
105 | };
106 |
107 | IndexPage.propTypes = {
108 | data: PropTypes.shape({
109 | markdownRemark: PropTypes.shape({
110 | frontmatter: PropTypes.object,
111 | }),
112 | }),
113 | };
114 |
115 | export default IndexPage;
116 |
117 | export const pageQuery = graphql`
118 | query IndexPageTemplate {
119 | markdownRemark(frontmatter: { templateKey: { eq: "index-page" } }) {
120 | frontmatter {
121 | title
122 | image {
123 | childImageSharp {
124 | gatsbyImageData(quality: 100, layout: FULL_WIDTH)
125 | }
126 | }
127 | heading
128 | subheading
129 | mainpitch {
130 | title
131 | description
132 | }
133 | description
134 | intro {
135 | blurbs {
136 | image {
137 | childImageSharp {
138 | gatsbyImageData(width: 240, quality: 64, layout: CONSTRAINED)
139 | }
140 | }
141 | text
142 | }
143 | heading
144 | description
145 | }
146 | }
147 | }
148 | }
149 | `;
150 |
--------------------------------------------------------------------------------
/src/templates/product-page.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { graphql } from "gatsby";
4 | import { getImage } from "gatsby-plugin-image";
5 | import Layout from "../components/Layout";
6 | import Features from "../components/Features";
7 | import Testimonials from "../components/Testimonials";
8 | import Pricing from "../components/Pricing";
9 | import PreviewCompatibleImage from "../components/PreviewCompatibleImage";
10 | import FullWidthImage from "../components/FullWidthImage";
11 |
12 | // eslint-disable-next-line
13 | export const ProductPageTemplate = ({
14 | image,
15 | title,
16 | heading,
17 | description,
18 | intro,
19 | main,
20 | testimonials,
21 | fullImage,
22 | pricing,
23 | }) => {
24 | const heroImage = getImage(image) || image;
25 | const fullWidthImage = getImage(fullImage) || fullImage;
26 |
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | {heading}
37 |
38 |
{description}
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | {main.heading}
48 |
49 |
{main.description}
50 |
51 |
52 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | {pricing.heading}
87 |
88 |
{pricing.description}
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 | );
97 | };
98 |
99 | ProductPageTemplate.propTypes = {
100 | image: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
101 | title: PropTypes.string,
102 | heading: PropTypes.string,
103 | description: PropTypes.string,
104 | intro: PropTypes.shape({
105 | blurbs: PropTypes.array,
106 | }),
107 | main: PropTypes.shape({
108 | heading: PropTypes.string,
109 | description: PropTypes.string,
110 | image1: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
111 | image2: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
112 | image3: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
113 | }),
114 | testimonials: PropTypes.array,
115 | fullImage: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
116 | pricing: PropTypes.shape({
117 | heading: PropTypes.string,
118 | description: PropTypes.string,
119 | plans: PropTypes.array,
120 | }),
121 | };
122 |
123 | const ProductPage = ({ data }) => {
124 | const { frontmatter } = data.markdownRemark;
125 |
126 | return (
127 |
128 |
139 |
140 | );
141 | };
142 |
143 | ProductPage.propTypes = {
144 | data: PropTypes.shape({
145 | markdownRemark: PropTypes.shape({
146 | frontmatter: PropTypes.object,
147 | }),
148 | }),
149 | };
150 |
151 | export default ProductPage;
152 |
153 | export const productPageQuery = graphql`
154 | query ProductPage($id: String!) {
155 | markdownRemark(id: { eq: $id }) {
156 | frontmatter {
157 | title
158 | image {
159 | childImageSharp {
160 | gatsbyImageData(quality: 100, layout: FULL_WIDTH)
161 | }
162 | }
163 | heading
164 | description
165 | intro {
166 | blurbs {
167 | image {
168 | childImageSharp {
169 | gatsbyImageData(width: 240, quality: 64, layout: CONSTRAINED)
170 | }
171 | }
172 | text
173 | }
174 | heading
175 | description
176 | }
177 | main {
178 | heading
179 | description
180 | image1 {
181 | alt
182 | image {
183 | childImageSharp {
184 | gatsbyImageData(width: 526, quality: 92, layout: CONSTRAINED)
185 | }
186 | }
187 | }
188 | image2 {
189 | alt
190 | image {
191 | childImageSharp {
192 | gatsbyImageData(width: 526, quality: 92, layout: CONSTRAINED)
193 | }
194 | }
195 | }
196 | image3 {
197 | alt
198 | image {
199 | childImageSharp {
200 | gatsbyImageData(quality: 72, layout: FULL_WIDTH)
201 | }
202 | }
203 | }
204 | }
205 | testimonials {
206 | author
207 | quote
208 | }
209 |
210 | full_image {
211 | childImageSharp {
212 | gatsbyImageData(quality: 100, layout: FULL_WIDTH)
213 | }
214 | }
215 | pricing {
216 | heading
217 | description
218 | plans {
219 | description
220 | items
221 | plan
222 | price
223 | }
224 | }
225 | }
226 | }
227 | }
228 | `;
229 |
--------------------------------------------------------------------------------
/src/templates/tags.js:
--------------------------------------------------------------------------------
1 | import * as React from "react";
2 | import { Helmet } from "react-helmet";
3 | import { Link, graphql } from "gatsby";
4 | import Layout from "../components/Layout";
5 |
6 | const TagRoute = (props) => {
7 |
8 | const posts = props.data.allMarkdownRemark.edges;
9 |
10 | const postLinks = posts.map((post) => (
11 |
12 |
13 | {post.node.frontmatter.title}
14 |
15 |
16 | ));
17 |
18 | const { tag } = props.pageContext;
19 | const { title } = props.data.site.siteMetadata;
20 | const { totalCount } = props.data.allMarkdownRemark;
21 | const tagHeader = `${totalCount} post${
22 | totalCount === 1 ? "" : "s"
23 | } tagged with “${tag}”`;
24 |
25 | return (
26 |
27 |
28 |
29 |
30 |
31 |
35 |
{tagHeader}
36 |
37 |
38 | Browse all tags
39 |
40 |
41 |
42 |
43 |
44 |
45 | );
46 | }
47 |
48 | export default TagRoute;
49 |
50 | export const tagPageQuery = graphql`
51 | query TagPage($tag: String) {
52 | site {
53 | siteMetadata {
54 | title
55 | }
56 | }
57 | allMarkdownRemark(
58 | limit: 1000
59 | sort: { fields: [frontmatter___date], order: DESC }
60 | filter: { frontmatter: { tags: { in: [$tag] } } }
61 | ) {
62 | totalCount
63 | edges {
64 | node {
65 | fields {
66 | slug
67 | }
68 | frontmatter {
69 | title
70 | }
71 | }
72 | }
73 | }
74 | }
75 | `;
76 |
--------------------------------------------------------------------------------
/static/admin/config.yml:
--------------------------------------------------------------------------------
1 | backend:
2 | name: git-gateway
3 | branch: main
4 | commit_messages:
5 | create: "Create {{collection}} “{{slug}}”"
6 | update: "Update {{collection}} “{{slug}}”"
7 | delete: "Delete {{collection}} “{{slug}}”"
8 | uploadMedia: "[skip ci] Upload “{{path}}”"
9 | deleteMedia: "[skip ci] Delete “{{path}}”"
10 |
11 | local_backend: true
12 | media_folder: static/img
13 | public_folder: /img
14 |
15 | collections:
16 | - name: "blog"
17 | label: "Blog"
18 | folder: "src/pages/blog"
19 | create: true
20 | slug: "{{year}}-{{month}}-{{day}}-{{slug}}"
21 | fields:
22 | - {
23 | label: "Template Key",
24 | name: "templateKey",
25 | widget: "hidden",
26 | default: "blog-post",
27 | }
28 | - { label: "Title", name: "title", widget: "string" }
29 | - { label: "Publish Date", name: "date", widget: "datetime" }
30 | - { label: "Description", name: "description", widget: "text" }
31 | - { label: "Featured Post", name: "featuredpost", widget: "boolean" }
32 | - { label: "Featured Image", name: "featuredimage", widget: image }
33 | - { label: "Body", name: "body", widget: "markdown" }
34 | - { label: "Tags", name: "tags", widget: "list" }
35 |
36 | - name: "pages"
37 | label: "Pages"
38 | files:
39 | - file: "src/pages/index.md"
40 | label: "Landing Page"
41 | name: "index"
42 | fields:
43 | - {
44 | label: "Template Key",
45 | name: "templateKey",
46 | widget: "hidden",
47 | default: "index-page",
48 | }
49 | - { label: Title, name: title, widget: string }
50 | - { label: Image, name: image, widget: image }
51 | - { label: Heading, name: heading, widget: string }
52 | - { label: Subheading, name: subheading, widget: string }
53 | - {
54 | label: Mainpitch,
55 | name: mainpitch,
56 | widget: object,
57 | fields:
58 | [
59 | { label: Title, name: title, widget: string },
60 | { label: Description, name: description, widget: text },
61 | ],
62 | }
63 | - { label: Description, name: description, widget: string }
64 | - {
65 | label: Intro,
66 | name: intro,
67 | widget: object,
68 | fields:
69 | [
70 | { label: Heading, name: heading, widget: string },
71 | { label: Description, name: description, widget: text },
72 | {
73 | label: Blurbs,
74 | name: blurbs,
75 | widget: list,
76 | fields:
77 | [
78 | { label: Image, name: image, widget: image },
79 | { label: Text, name: text, widget: text },
80 | ],
81 | },
82 | ],
83 | }
84 | - {
85 | label: Main,
86 | name: main,
87 | widget: object,
88 | fields:
89 | [
90 | { label: Heading, name: heading, widget: string },
91 | { label: Description, name: description, widget: text },
92 | {
93 | label: Image1,
94 | name: image1,
95 | widget: object,
96 | fields:
97 | [
98 | { label: Image, name: image, widget: image },
99 | { label: Alt, name: alt, widget: string },
100 | ],
101 | },
102 | {
103 | label: Image2,
104 | name: image2,
105 | widget: object,
106 | fields:
107 | [
108 | { label: Image, name: image, widget: image },
109 | { label: Alt, name: alt, widget: string },
110 | ],
111 | },
112 | {
113 | label: Image3,
114 | name: image3,
115 | widget: object,
116 | fields:
117 | [
118 | { label: Image, name: image, widget: image },
119 | { label: Alt, name: alt, widget: string },
120 | ],
121 | },
122 | ],
123 | }
124 | - file: "src/pages/about/index.md"
125 | label: "About"
126 | name: "about"
127 | fields:
128 | - {
129 | label: "Template Key",
130 | name: "templateKey",
131 | widget: "hidden",
132 | default: "about-page",
133 | }
134 | - { label: "Title", name: "title", widget: "string" }
135 | - { label: "Body", name: "body", widget: "markdown" }
136 | - file: "src/pages/products/index.md"
137 | label: "Products Page"
138 | name: "products"
139 | fields:
140 | - {
141 | label: "Template Key",
142 | name: "templateKey",
143 | widget: "hidden",
144 | default: "product-page",
145 | }
146 | - { label: Title, name: title, widget: string }
147 | - { label: Image, name: image, widget: image }
148 | - { label: Heading, name: heading, widget: string }
149 | - { label: Description, name: description, widget: string }
150 | - {
151 | label: Intro,
152 | name: intro,
153 | widget: object,
154 | fields:
155 | [
156 | { label: Heading, name: heading, widget: string },
157 | { label: Description, name: description, widget: text },
158 | {
159 | label: Blurbs,
160 | name: blurbs,
161 | widget: list,
162 | fields:
163 | [
164 | { label: Image, name: image, widget: image },
165 | { label: Text, name: text, widget: text },
166 | ],
167 | },
168 | ],
169 | }
170 | - {
171 | label: Main,
172 | name: main,
173 | widget: object,
174 | fields:
175 | [
176 | { label: Heading, name: heading, widget: string },
177 | { label: Description, name: description, widget: text },
178 | {
179 | label: Image1,
180 | name: image1,
181 | widget: object,
182 | fields:
183 | [
184 | { label: Image, name: image, widget: image },
185 | { label: Alt, name: alt, widget: string },
186 | ],
187 | },
188 | {
189 | label: Image2,
190 | name: image2,
191 | widget: object,
192 | fields:
193 | [
194 | { label: Image, name: image, widget: image },
195 | { label: Alt, name: alt, widget: string },
196 | ],
197 | },
198 | {
199 | label: Image3,
200 | name: image3,
201 | widget: object,
202 | fields:
203 | [
204 | { label: Image, name: image, widget: image },
205 | { label: Alt, name: alt, widget: string },
206 | ],
207 | },
208 | ],
209 | }
210 | - {
211 | label: Testimonials,
212 | name: testimonials,
213 | widget: list,
214 | fields:
215 | [
216 | { label: Quote, name: quote, widget: string },
217 | { label: Author, name: author, widget: string },
218 | ],
219 | }
220 | - { label: Full_image, name: full_image, widget: image }
221 | - {
222 | label: Pricing,
223 | name: pricing,
224 | widget: object,
225 | fields:
226 | [
227 | { label: Heading, name: heading, widget: string },
228 | { label: Description, name: description, widget: string },
229 | {
230 | label: Plans,
231 | name: plans,
232 | widget: list,
233 | fields:
234 | [
235 | { label: Plan, name: plan, widget: string },
236 | { label: Price, name: price, widget: string },
237 | {
238 | label: Description,
239 | name: description,
240 | widget: string,
241 | },
242 | { label: Items, name: items, widget: list },
243 | ],
244 | },
245 | ],
246 | }
247 |
--------------------------------------------------------------------------------
/static/img/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/apple-touch-icon.png
--------------------------------------------------------------------------------
/static/img/blog-index.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/blog-index.jpg
--------------------------------------------------------------------------------
/static/img/chemex.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/chemex.jpg
--------------------------------------------------------------------------------
/static/img/coffee-gear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/coffee-gear.png
--------------------------------------------------------------------------------
/static/img/coffee.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/coffee.png
--------------------------------------------------------------------------------
/static/img/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/favicon-16x16.png
--------------------------------------------------------------------------------
/static/img/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/favicon-32x32.png
--------------------------------------------------------------------------------
/static/img/flavor_wheel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/flavor_wheel.jpg
--------------------------------------------------------------------------------
/static/img/home-jumbotron.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/home-jumbotron.jpg
--------------------------------------------------------------------------------
/static/img/jumbotron.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/jumbotron.jpg
--------------------------------------------------------------------------------
/static/img/logo.svg:
--------------------------------------------------------------------------------
1 | Logo
2 |
--------------------------------------------------------------------------------
/static/img/meeting-space.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/meeting-space.png
--------------------------------------------------------------------------------
/static/img/og-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/og-image.jpg
--------------------------------------------------------------------------------
/static/img/products-full-width.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/products-full-width.jpg
--------------------------------------------------------------------------------
/static/img/products-grid1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/products-grid1.jpg
--------------------------------------------------------------------------------
/static/img/products-grid2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/products-grid2.jpg
--------------------------------------------------------------------------------
/static/img/products-grid3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/products-grid3.jpg
--------------------------------------------------------------------------------
/static/img/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.11, written by Peter Selinger 2001-2013
9 |
10 |
12 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/static/img/tutorials.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/multijump/gatsby-starter-netlify-cms/8a3f4a6f03049dd4ea0bd1a933b0dd5d463ce3e1/static/img/tutorials.png
--------------------------------------------------------------------------------