├── .gitignore ├── .gitpod.yml ├── .netlify └── state.json ├── .travis.yml ├── LICENSE ├── README.md ├── functions ├── .gitkeep └── multipart-file-upload │ ├── multipart-file-upload.js │ ├── package-lock.json │ └── package.json ├── gatsby-config.js ├── netlify.toml ├── package.json ├── src ├── css │ ├── styles.css │ └── typography.css ├── layout.js └── pages │ ├── 404.js │ ├── contact.js │ ├── file-upload.js │ ├── index.js │ ├── multipart-file-upload.js │ ├── recaptcha.js │ └── thanks.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | .gatsby-context.js 29 | .sass-cache/ 30 | public/ 31 | .cache/ 32 | .DS_Store 33 | 34 | # Environment variables 35 | .env.development 36 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | ## optional config to try this out in gitpod 2 | ## feel free to delete 3 | ports: 4 | - port: 8000 5 | onOpen: open-preview 6 | tasks: 7 | - init: yarn install 8 | command: yarn develop 9 | -------------------------------------------------------------------------------- /.netlify/state.json: -------------------------------------------------------------------------------- 1 | { 2 | "siteId": "761206b6-2410-4027-ab0b-10b8730b356d" 3 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # back to language cpp to try to bypass osx node failure 2 | language: cpp 3 | sudo: false 4 | env: 5 | - export NODE_VERSION="0.12" 6 | - export NODE_VERSION="4" 7 | - export NODE_VERSION="5" 8 | os: 9 | - linux 10 | - osx 11 | # pre-install to bring in the correct version of node via nvm 12 | before_install: 13 | - git submodule update --init --recursive 14 | - git clone https://github.com/creationix/nvm.git ./.nvm 15 | - source ./.nvm/nvm.sh 16 | - nvm install $NODE_VERSION 17 | - nvm use $NODE_VERSION 18 | - npm config set python `which python` 19 | - if [ $TRAVIS_OS_NAME == "linux" ]; then 20 | export CC="gcc-4.8"; 21 | export CXX="g++-4.8"; 22 | export LINK="gcc-4.8"; 23 | export LINKXX="g++-4.8"; 24 | fi 25 | - gcc --version 26 | - g++ --version 27 | # node 4 depends on gcc 4.8 28 | addons: 29 | apt: 30 | sources: 31 | - ubuntu-toolchain-r-test 32 | packages: 33 | - g++-4.8 34 | - gcc-4.8 35 | # script needed to test, because defaults don't work on osx 36 | script: 37 | - npm install 38 | - npm run lint 39 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > Note: I no longer work at Netlify - if you'd like to maintain this example repo, please get in touch and I'd be happy to transfer. 2 | 3 | # Integrating Netlify Form Handling in Gatsby 4 | 5 | Example for integrating a basic contact form with Netlify’s form handling feature. 6 | 7 | Demo: https://gatsby-netlify-form-example-v2.netlify.com/ 8 | 9 | Note: You can also find a [Gatsby + Netlify Forms example in the Gatsby+NetlifyCMS starter](https://gatsby-netlify-cms.netlify.com/contact/examples). 10 | 11 | Features: 12 | 13 | - Basic form submission 14 | - File upload 15 | - Form submission with reCAPTCHA 16 | 17 | All examples use controlled forms to offer more flexibility, but you can use uncontrolled forms too. 18 | 19 | More information in the blogpost: https://www.netlify.com/blog/2017/07/20/how-to-integrate-netlifys-form-handling-in-a-react-app/ 20 | 21 | ## Deploy 22 | 23 | [![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/sw-yx/gatsby-netlify-form-example-v2) 24 | 25 | ## Try it out in Gitpod 26 | 27 | [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/from-referrer/) 28 | 29 | ## How it Looks on Netlify app 30 | 31 | ![CleanShot 2021-08-29 at 16 40 57](https://user-images.githubusercontent.com/6764957/131269107-3272201f-9c68-4328-869d-a02330bffc8b.png) 32 | 33 | 34 | ![CleanShot 2021-08-29 at 16 41 17](https://user-images.githubusercontent.com/6764957/131269117-7538b676-fb41-44e6-af74-0d67f56afecb.png) 35 | 36 | 37 | ## reCAPTCHA 38 | 39 | This example site uses [react-google-recaptcha](https://github.com/dozoisch/react-google-recaptcha) to render the reCAPTCHA widget. 40 | 41 | To make the reCAPTCHA example work in your own copy of this site, you’ll need to do the following: 42 | 43 | 1. [Sign up for a reCAPTCHA API key pair](http://www.google.com/recaptcha/admin) for your site. 44 | 2. [Log in to your Netlify account](https://app.netlify.com), and add the following 45 | environment variables to your site’s Settings > Build & deploy > Build environment variables: 46 | 47 | - `SITE_RECAPTCHA_KEY` with your reCAPTCHA site key. 48 | - `SITE_RECAPTCHA_SECRET` with your reCAPTCHA secret key. 49 | 50 | **Important**: the environment variables need to be called `SITE_RECAPTCHA_KEY` and `SITE_RECAPTCHA_SECRET` for the Netlify backend to find them. If you add a `GATSBY_` prefix to the variable names, the Netlify backend won't recognize them, the reCAPTCHA verification will fail, and your form submissions won't be stored. 51 | 52 | If you still need the key on the frontend, like shown in this demo, you can simply duplicate the key: 53 | 54 | ![image](https://user-images.githubusercontent.com/6764957/79165052-e8e52b00-7e14-11ea-851f-55ae51e6f1f6.png) 55 | 56 | 3. Change the build command for your site to 57 | 58 | ``` 59 | echo SITE_RECAPTCHA_KEY=$SITE_RECAPTCHA_KEY >> .env.production && gatsby build 60 | ``` 61 | 62 | This will make the SITE_RECAPTCHA_KEY available to the Gatsby build in production. 63 | 64 | To see the reCAPTCHA widget locally, add `SITE_RECAPTCHA_KEY=your-reCAPTCHA-API-site-key` 65 | to your local [.env.development](https://www.gatsbyjs.org/docs/environment-variables/) file. 66 | -------------------------------------------------------------------------------- /functions/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swyxio/gatsby-netlify-form-example-v2/4be1edabbb44808009b06dc72f97e21c645242f3/functions/.gitkeep -------------------------------------------------------------------------------- /functions/multipart-file-upload/multipart-file-upload.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const fetch = require('node-fetch') 3 | var FormData = require('form-data') 4 | const util = require('util') 5 | 6 | function encode(data) { 7 | const formData = new FormData() 8 | 9 | for (const key of Object.keys(data)) { 10 | formData.append(key, data[key]) 11 | } 12 | 13 | return formData 14 | } 15 | 16 | exports.handler = async function(event, context) { 17 | const body = event 18 | const form = encode(event.body) 19 | try { 20 | await util.promisify((err, res) => 21 | form.submit('https://gatsby-netlify-form-example-v2.netlify.com', function(err, res) { 22 | // res – response object (http.IncomingMessage) // 23 | res.resume() 24 | }), 25 | ) 26 | return { 27 | statusCode: 200, 28 | body: 'gucci', 29 | } 30 | } catch (err) { 31 | console.log(err) // output to netlify function log 32 | return { 33 | statusCode: 500, 34 | body: JSON.stringify({ msg: err.message }), // Could be a custom message or object i.e. JSON.stringify(err) 35 | } 36 | } 37 | 38 | // try { 39 | // const response = await fetch("/", { 40 | // headers: { Accept: "application/json" } 41 | // }); 42 | // if (!response.ok) { 43 | // // NOT res.status >= 200 && res.status < 300 44 | // return { statusCode: response.status, body: response.statusText }; 45 | // } 46 | // const data = await response.json(); 47 | 48 | // return { 49 | // statusCode: 200, 50 | // body: JSON.stringify({ msg: data.joke }) 51 | // }; 52 | // } catch (err) { 53 | // console.log(err); // output to netlify function log 54 | // return { 55 | // statusCode: 500, 56 | // body: JSON.stringify({ msg: err.message }) // Could be a custom message or object i.e. JSON.stringify(err) 57 | // }; 58 | // } 59 | } 60 | -------------------------------------------------------------------------------- /functions/multipart-file-upload/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-fetch", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "asynckit": { 8 | "version": "0.4.0", 9 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 10 | "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" 11 | }, 12 | "combined-stream": { 13 | "version": "1.0.8", 14 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 15 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 16 | "requires": { 17 | "delayed-stream": "~1.0.0" 18 | } 19 | }, 20 | "delayed-stream": { 21 | "version": "1.0.0", 22 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 23 | "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" 24 | }, 25 | "form-data": { 26 | "version": "2.5.0", 27 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.0.tgz", 28 | "integrity": "sha512-WXieX3G/8side6VIqx44ablyULoGruSde5PNTxoUyo5CeyAMX6nVWUd0rgist/EuX655cjhUhTo1Fo3tRYqbcA==", 29 | "requires": { 30 | "asynckit": "^0.4.0", 31 | "combined-stream": "^1.0.6", 32 | "mime-types": "^2.1.12" 33 | } 34 | }, 35 | "mime-db": { 36 | "version": "1.40.0", 37 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", 38 | "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" 39 | }, 40 | "mime-types": { 41 | "version": "2.1.24", 42 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", 43 | "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", 44 | "requires": { 45 | "mime-db": "1.40.0" 46 | } 47 | }, 48 | "node-fetch": { 49 | "version": "2.6.0", 50 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", 51 | "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /functions/multipart-file-upload/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-fetch", 3 | "version": "1.0.0", 4 | "description": "netlify functions:create - default template for node fetch function", 5 | "main": "node-fetch.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "netlify", 11 | "serverless", 12 | "js" 13 | ], 14 | "author": "Netlify", 15 | "license": "MIT", 16 | "dependencies": { 17 | "form-data": "^2.5.0", 18 | "node-fetch": "^2.3.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { 3 | siteMetadata: { 4 | title: `Gatsby Netlify Form Integration` 5 | }, 6 | plugins: [`gatsby-plugin-react-helmet`] 7 | }; 8 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | # example netlify.toml 2 | [build] 3 | command = "yarn run build" 4 | functions = "functions" 5 | publish = "public" 6 | 7 | ## more info https://www.netlify.com/docs/netlify-toml-reference/ 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-netlify-form-example", 3 | "description": "Gatsby Netlify Form example", 4 | "version": "1.0.0", 5 | "author": "swyx ", 6 | "dependencies": { 7 | "gatsby": "^2.13.35", 8 | "gatsby-plugin-react-helmet": "^3.1.2", 9 | "netlify-lambda": "^1.6.0", 10 | "react": "^16.8.6", 11 | "react-async-script": "^0.9.1", 12 | "react-dom": "^16.8.6", 13 | "react-google-recaptcha": "^1.1.0", 14 | "react-helmet": "^5.2.1" 15 | }, 16 | "devDependencies": { 17 | "dotenv": "^5.0.1" 18 | }, 19 | "keywords": [ 20 | "gatsby" 21 | ], 22 | "license": "MIT", 23 | "main": "n/a", 24 | "scripts": { 25 | "postinstall": "netlify-lambda install", 26 | "build": "gatsby build", 27 | "develop": "gatsby develop", 28 | "format": "prettier --trailing-comma es5 --no-semi --single-quote --write \"pages/*.js\" \"utils/*.js\" \"wrappers/*.js\" \"html.js\"", 29 | "test": "echo \"Error: no test specified\" && exit 1" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/css/styles.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif; 3 | -ms-text-size-adjust: 100%; 4 | -webkit-text-size-adjust: 100%; 5 | } 6 | body { 7 | margin: 0; 8 | } 9 | article, 10 | aside, 11 | details, 12 | figcaption, 13 | figure, 14 | footer, 15 | header, 16 | main, 17 | menu, 18 | nav, 19 | section, 20 | summary { 21 | display: block; 22 | } 23 | audio, 24 | canvas, 25 | progress, 26 | video { 27 | display: inline-block; 28 | } 29 | audio:not([controls]) { 30 | display: none; 31 | height: 0; 32 | } 33 | progress { 34 | vertical-align: baseline; 35 | } 36 | [hidden], 37 | template { 38 | display: none; 39 | } 40 | a { 41 | background-color: transparent; 42 | -webkit-text-decoration-skip: objects; 43 | } 44 | a:active, 45 | a:hover { 46 | outline-width: 0; 47 | } 48 | abbr[title] { 49 | border-bottom: none; 50 | text-decoration: underline; 51 | text-decoration: underline dotted; 52 | } 53 | b, 54 | strong { 55 | font-weight: inherit; 56 | font-weight: bolder; 57 | } 58 | dfn { 59 | font-style: italic; 60 | } 61 | h1 { 62 | font-size: 2em; 63 | margin: 0.67em 0; 64 | } 65 | mark { 66 | background-color: #ff0; 67 | color: #000; 68 | } 69 | small { 70 | font-size: 80%; 71 | } 72 | sub, 73 | sup { 74 | font-size: 75%; 75 | line-height: 0; 76 | position: relative; 77 | vertical-align: baseline; 78 | } 79 | sub { 80 | bottom: -0.25em; 81 | } 82 | sup { 83 | top: -0.5em; 84 | } 85 | img { 86 | border-style: none; 87 | } 88 | svg:not(:root) { 89 | overflow: hidden; 90 | } 91 | code, 92 | kbd, 93 | pre, 94 | samp { 95 | font-family: monospace, monospace; 96 | font-size: 1em; 97 | } 98 | figure { 99 | margin: 1em 40px; 100 | } 101 | hr { 102 | box-sizing: content-box; 103 | height: 0; 104 | overflow: visible; 105 | } 106 | button, 107 | input, 108 | optgroup, 109 | select, 110 | textarea { 111 | font: inherit; 112 | margin: 0; 113 | } 114 | optgroup { 115 | font-weight: 700; 116 | } 117 | button, 118 | input { 119 | overflow: visible; 120 | } 121 | button, 122 | select { 123 | text-transform: none; 124 | } 125 | [type='reset'], 126 | [type='submit'], 127 | button, 128 | html [type='button'] { 129 | -webkit-appearance: button; 130 | } 131 | [type='button']::-moz-focus-inner, 132 | [type='reset']::-moz-focus-inner, 133 | [type='submit']::-moz-focus-inner, 134 | button::-moz-focus-inner { 135 | border-style: none; 136 | padding: 0; 137 | } 138 | [type='button']:-moz-focusring, 139 | [type='reset']:-moz-focusring, 140 | [type='submit']:-moz-focusring, 141 | button:-moz-focusring { 142 | outline: 1px dotted ButtonText; 143 | } 144 | fieldset { 145 | border: 1px solid silver; 146 | margin: 0 2px; 147 | padding: 0.35em 0.625em 0.75em; 148 | } 149 | legend { 150 | box-sizing: border-box; 151 | color: inherit; 152 | display: table; 153 | max-width: 100%; 154 | padding: 0; 155 | white-space: normal; 156 | } 157 | textarea { 158 | overflow: auto; 159 | } 160 | [type='checkbox'], 161 | [type='radio'] { 162 | box-sizing: border-box; 163 | padding: 0; 164 | } 165 | [type='number']::-webkit-inner-spin-button, 166 | [type='number']::-webkit-outer-spin-button { 167 | height: auto; 168 | } 169 | [type='search'] { 170 | -webkit-appearance: textfield; 171 | outline-offset: -2px; 172 | } 173 | [type='search']::-webkit-search-cancel-button, 174 | [type='search']::-webkit-search-decoration { 175 | -webkit-appearance: none; 176 | } 177 | ::-webkit-input-placeholder { 178 | color: inherit; 179 | opacity: 0.54; 180 | } 181 | ::-webkit-file-upload-button { 182 | -webkit-appearance: button; 183 | font: inherit; 184 | } 185 | html { 186 | font: 112.5%/1.45em georgia, serif; 187 | box-sizing: border-box; 188 | overflow-y: scroll; 189 | } 190 | *, 191 | :after, 192 | :before { 193 | box-sizing: inherit; 194 | } 195 | body { 196 | color: rgba(0, 0, 0, 0.8); 197 | font-family: georgia, serif; 198 | font-weight: 400; 199 | word-wrap: break-word; 200 | -webkit-font-kerning: normal; 201 | font-kerning: normal; 202 | -ms-font-feature-settings: 'kern', 'liga', 'clig', 'calt'; 203 | -webkit-font-feature-settings: 'kern', 'liga', 'clig', 'calt'; 204 | font-feature-settings: 'kern', 'liga', 'clig', 'calt', 'kern'; 205 | } 206 | img { 207 | max-width: 100%; 208 | margin: 0 0 1.45rem; 209 | padding: 0; 210 | } 211 | h1 { 212 | font-size: 2.25rem; 213 | line-height: 2.9rem; 214 | } 215 | h1, 216 | h2 { 217 | margin: 0 0 1.45rem; 218 | padding: 0; 219 | color: inherit; 220 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, 221 | Helvetica Neue, sans-serif; 222 | font-weight: 700; 223 | text-rendering: optimizeLegibility; 224 | } 225 | h2 { 226 | font-size: 1.62671rem; 227 | line-height: 2.175rem; 228 | } 229 | h3 { 230 | font-size: 1.38316rem; 231 | line-height: 2.175rem; 232 | } 233 | h3, 234 | h4 { 235 | margin: 0 0 1.45rem; 236 | padding: 0; 237 | color: inherit; 238 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, 239 | Helvetica Neue, sans-serif; 240 | font-weight: 700; 241 | text-rendering: optimizeLegibility; 242 | } 243 | h4 { 244 | font-size: 1rem; 245 | line-height: 1.45rem; 246 | } 247 | h5 { 248 | font-size: 0.85028rem; 249 | } 250 | h5, 251 | h6 { 252 | margin: 0 0 1.45rem; 253 | padding: 0; 254 | color: inherit; 255 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, 256 | Helvetica Neue, sans-serif; 257 | font-weight: 700; 258 | text-rendering: optimizeLegibility; 259 | line-height: 1.45rem; 260 | } 261 | h6 { 262 | font-size: 0.78405rem; 263 | } 264 | hgroup { 265 | margin: 0 0 1.45rem; 266 | padding: 0; 267 | } 268 | ol, 269 | ul { 270 | margin: 0 0 1.45rem 1.45rem; 271 | padding: 0; 272 | list-style-position: outside; 273 | list-style-image: none; 274 | } 275 | dd, 276 | dl, 277 | figure, 278 | p { 279 | margin: 0 0 1.45rem; 280 | padding: 0; 281 | } 282 | pre { 283 | padding: 0; 284 | font-size: 0.85rem; 285 | line-height: 1.42; 286 | background: rgba(0, 0, 0, 0.04); 287 | border-radius: 3px; 288 | overflow: auto; 289 | word-wrap: normal; 290 | padding: 1.45rem; 291 | } 292 | pre, 293 | table { 294 | margin: 0 0 1.45rem; 295 | } 296 | table { 297 | padding: 0; 298 | font-size: 1rem; 299 | line-height: 1.45rem; 300 | border-collapse: collapse; 301 | width: 100%; 302 | } 303 | fieldset { 304 | margin: 0 0 1.45rem; 305 | padding: 0; 306 | } 307 | blockquote { 308 | margin: 0 1.45rem 1.45rem; 309 | padding: 0; 310 | } 311 | form, 312 | iframe, 313 | noscript { 314 | margin: 0 0 1.45rem; 315 | padding: 0; 316 | } 317 | hr { 318 | margin: 0 0 calc(1.45rem - 1px); 319 | padding: 0; 320 | background: rgba(0, 0, 0, 0.2); 321 | border: none; 322 | height: 1px; 323 | } 324 | address { 325 | margin: 0 0 1.45rem; 326 | padding: 0; 327 | } 328 | b, 329 | dt, 330 | strong, 331 | th { 332 | font-weight: 700; 333 | } 334 | li { 335 | margin-bottom: 0.725rem; 336 | } 337 | ol li, 338 | ul li { 339 | padding-left: 0; 340 | } 341 | li > ol, 342 | li > ul { 343 | margin-left: 1.45rem; 344 | margin-bottom: 0.725rem; 345 | margin-top: 0.725rem; 346 | } 347 | blockquote :last-child, 348 | li :last-child, 349 | p :last-child { 350 | margin-bottom: 0; 351 | } 352 | li > p { 353 | margin-bottom: 0.725rem; 354 | } 355 | code, 356 | kbd, 357 | samp { 358 | font-size: 0.85rem; 359 | line-height: 1.45rem; 360 | } 361 | abbr, 362 | abbr[title], 363 | acronym { 364 | border-bottom: 1px dotted rgba(0, 0, 0, 0.5); 365 | cursor: help; 366 | } 367 | abbr[title] { 368 | text-decoration: none; 369 | } 370 | td, 371 | th, 372 | thead { 373 | text-align: left; 374 | } 375 | td, 376 | th { 377 | border-bottom: 1px solid rgba(0, 0, 0, 0.12); 378 | font-feature-settings: 'tnum'; 379 | -moz-font-feature-settings: 'tnum'; 380 | -ms-font-feature-settings: 'tnum'; 381 | -webkit-font-feature-settings: 'tnum'; 382 | padding: 0.725rem 0.96667rem calc(0.725rem - 1px); 383 | } 384 | td:first-child, 385 | th:first-child { 386 | padding-left: 0; 387 | } 388 | td:last-child, 389 | th:last-child { 390 | padding-right: 0; 391 | } 392 | code, 393 | tt { 394 | background-color: rgba(0, 0, 0, 0.04); 395 | border-radius: 3px; 396 | font-family: SFMono-Regular, Consolas, Roboto Mono, Droid Sans Mono, Liberation Mono, Menlo, Courier, monospace; 397 | padding: 0; 398 | padding-top: 0.2em; 399 | padding-bottom: 0.2em; 400 | } 401 | pre code { 402 | background: none; 403 | line-height: 1.42; 404 | } 405 | code:after, 406 | code:before, 407 | tt:after, 408 | tt:before { 409 | letter-spacing: -0.2em; 410 | content: ' '; 411 | } 412 | pre code:after, 413 | pre code:before, 414 | pre tt:after, 415 | pre tt:before { 416 | content: ''; 417 | } 418 | @media only screen and (max-width: 480px) { 419 | html { 420 | font-size: 100%; 421 | } 422 | } 423 | -------------------------------------------------------------------------------- /src/css/typography.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif; 3 | -ms-text-size-adjust: 100%; 4 | -webkit-text-size-adjust: 100%; 5 | } 6 | body { 7 | margin: 0; 8 | } 9 | article, 10 | aside, 11 | details, 12 | figcaption, 13 | figure, 14 | footer, 15 | header, 16 | main, 17 | menu, 18 | nav, 19 | section, 20 | summary { 21 | display: block; 22 | } 23 | audio, 24 | canvas, 25 | progress, 26 | video { 27 | display: inline-block; 28 | } 29 | audio:not([controls]) { 30 | display: none; 31 | height: 0; 32 | } 33 | progress { 34 | vertical-align: baseline; 35 | } 36 | [hidden], 37 | template { 38 | display: none; 39 | } 40 | a { 41 | background-color: transparent; 42 | -webkit-text-decoration-skip: objects; 43 | } 44 | a:active, 45 | a:hover { 46 | outline-width: 0; 47 | } 48 | abbr[title] { 49 | border-bottom: none; 50 | text-decoration: underline; 51 | text-decoration: underline dotted; 52 | } 53 | b, 54 | strong { 55 | font-weight: inherit; 56 | font-weight: bolder; 57 | } 58 | dfn { 59 | font-style: italic; 60 | } 61 | h1 { 62 | font-size: 2em; 63 | margin: .67em 0; 64 | } 65 | mark { 66 | background-color: #ff0; 67 | color: #000; 68 | } 69 | small { 70 | font-size: 80%; 71 | } 72 | sub, 73 | sup { 74 | font-size: 75%; 75 | line-height: 0; 76 | position: relative; 77 | vertical-align: baseline; 78 | } 79 | sub { 80 | bottom: -.25em; 81 | } 82 | sup { 83 | top: -.5em; 84 | } 85 | img { 86 | border-style: none; 87 | } 88 | svg:not(:root) { 89 | overflow: hidden; 90 | } 91 | code, 92 | kbd, 93 | pre, 94 | samp { 95 | font-family: monospace, monospace; 96 | font-size: 1em; 97 | } 98 | figure { 99 | margin: 1em 40px; 100 | } 101 | hr { 102 | box-sizing: content-box; 103 | height: 0; 104 | overflow: visible; 105 | } 106 | button, 107 | input, 108 | optgroup, 109 | select, 110 | textarea { 111 | font: inherit; 112 | margin: 0; 113 | } 114 | optgroup { 115 | font-weight: 700; 116 | } 117 | button, 118 | input { 119 | overflow: visible; 120 | } 121 | button, 122 | select { 123 | text-transform: none; 124 | } 125 | [type=reset], 126 | [type=submit], 127 | button, 128 | html [type=button] { 129 | -webkit-appearance: button; 130 | } 131 | [type=button]::-moz-focus-inner, 132 | [type=reset]::-moz-focus-inner, 133 | [type=submit]::-moz-focus-inner, 134 | button::-moz-focus-inner { 135 | border-style: none; 136 | padding: 0; 137 | } 138 | [type=button]:-moz-focusring, 139 | [type=reset]:-moz-focusring, 140 | [type=submit]:-moz-focusring, 141 | button:-moz-focusring { 142 | outline: 1px dotted ButtonText; 143 | } 144 | fieldset { 145 | border: 1px solid silver; 146 | margin: 0 2px; 147 | padding: .35em .625em .75em; 148 | } 149 | legend { 150 | box-sizing: border-box; 151 | color: inherit; 152 | display: table; 153 | max-width: 100%; 154 | padding: 0; 155 | white-space: normal; 156 | } 157 | textarea { 158 | overflow: auto; 159 | } 160 | [type=checkbox], 161 | [type=radio] { 162 | box-sizing: border-box; 163 | padding: 0; 164 | } 165 | [type=number]::-webkit-inner-spin-button, 166 | [type=number]::-webkit-outer-spin-button { 167 | height: auto; 168 | } 169 | [type=search] { 170 | -webkit-appearance: textfield; 171 | outline-offset: -2px; 172 | } 173 | [type=search]::-webkit-search-cancel-button, 174 | [type=search]::-webkit-search-decoration { 175 | -webkit-appearance: none; 176 | } 177 | ::-webkit-input-placeholder { 178 | color: inherit; 179 | opacity: .54; 180 | } 181 | ::-webkit-file-upload-button { 182 | -webkit-appearance: button; 183 | font: inherit; 184 | } 185 | html { 186 | font: 112.5%/1.45em georgia, serif; 187 | box-sizing: border-box; 188 | overflow-y: scroll; 189 | } 190 | * { 191 | box-sizing: inherit; 192 | } 193 | *:before { 194 | box-sizing: inherit; 195 | } 196 | *:after { 197 | box-sizing: inherit; 198 | } 199 | body { 200 | color: hsla(0, 0%, 0%, 0.8); 201 | font-family: georgia, serif; 202 | font-weight: normal; 203 | word-wrap: break-word; 204 | font-kerning: normal; 205 | -moz-font-feature-settings: "kern", "liga", "clig", "calt"; 206 | -ms-font-feature-settings: "kern", "liga", "clig", "calt"; 207 | -webkit-font-feature-settings: "kern", "liga", "clig", "calt"; 208 | font-feature-settings: "kern", "liga", "clig", "calt"; 209 | } 210 | img { 211 | max-width: 100%; 212 | margin-left: 0; 213 | margin-right: 0; 214 | margin-top: 0; 215 | padding-bottom: 0; 216 | padding-left: 0; 217 | padding-right: 0; 218 | padding-top: 0; 219 | margin-bottom: 1.45rem; 220 | } 221 | h1 { 222 | margin-left: 0; 223 | margin-right: 0; 224 | margin-top: 0; 225 | padding-bottom: 0; 226 | padding-left: 0; 227 | padding-right: 0; 228 | padding-top: 0; 229 | margin-bottom: 1.45rem; 230 | color: inherit; 231 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 232 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 233 | font-weight: bold; 234 | text-rendering: optimizeLegibility; 235 | font-size: 2.25rem; 236 | line-height: 2.9rem; 237 | } 238 | h2 { 239 | margin-left: 0; 240 | margin-right: 0; 241 | margin-top: 0; 242 | padding-bottom: 0; 243 | padding-left: 0; 244 | padding-right: 0; 245 | padding-top: 0; 246 | margin-bottom: 1.45rem; 247 | color: inherit; 248 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 249 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 250 | font-weight: bold; 251 | text-rendering: optimizeLegibility; 252 | font-size: 1.62671rem; 253 | line-height: 2.175rem; 254 | } 255 | h3 { 256 | margin-left: 0; 257 | margin-right: 0; 258 | margin-top: 0; 259 | padding-bottom: 0; 260 | padding-left: 0; 261 | padding-right: 0; 262 | padding-top: 0; 263 | margin-bottom: 1.45rem; 264 | color: inherit; 265 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 266 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 267 | font-weight: bold; 268 | text-rendering: optimizeLegibility; 269 | font-size: 1.38316rem; 270 | line-height: 2.175rem; 271 | } 272 | h4 { 273 | margin-left: 0; 274 | margin-right: 0; 275 | margin-top: 0; 276 | padding-bottom: 0; 277 | padding-left: 0; 278 | padding-right: 0; 279 | padding-top: 0; 280 | margin-bottom: 1.45rem; 281 | color: inherit; 282 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 283 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 284 | font-weight: bold; 285 | text-rendering: optimizeLegibility; 286 | font-size: 1rem; 287 | line-height: 1.45rem; 288 | } 289 | h5 { 290 | margin-left: 0; 291 | margin-right: 0; 292 | margin-top: 0; 293 | padding-bottom: 0; 294 | padding-left: 0; 295 | padding-right: 0; 296 | padding-top: 0; 297 | margin-bottom: 1.45rem; 298 | color: inherit; 299 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 300 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 301 | font-weight: bold; 302 | text-rendering: optimizeLegibility; 303 | font-size: 0.85028rem; 304 | line-height: 1.45rem; 305 | } 306 | h6 { 307 | margin-left: 0; 308 | margin-right: 0; 309 | margin-top: 0; 310 | padding-bottom: 0; 311 | padding-left: 0; 312 | padding-right: 0; 313 | padding-top: 0; 314 | margin-bottom: 1.45rem; 315 | color: inherit; 316 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 317 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 318 | font-weight: bold; 319 | text-rendering: optimizeLegibility; 320 | font-size: 0.78405rem; 321 | line-height: 1.45rem; 322 | } 323 | hgroup { 324 | margin-left: 0; 325 | margin-right: 0; 326 | margin-top: 0; 327 | padding-bottom: 0; 328 | padding-left: 0; 329 | padding-right: 0; 330 | padding-top: 0; 331 | margin-bottom: 1.45rem; 332 | } 333 | ul { 334 | margin-left: 1.45rem; 335 | margin-right: 0; 336 | margin-top: 0; 337 | padding-bottom: 0; 338 | padding-left: 0; 339 | padding-right: 0; 340 | padding-top: 0; 341 | margin-bottom: 1.45rem; 342 | list-style-position: outside; 343 | list-style-image: none; 344 | } 345 | ol { 346 | margin-left: 1.45rem; 347 | margin-right: 0; 348 | margin-top: 0; 349 | padding-bottom: 0; 350 | padding-left: 0; 351 | padding-right: 0; 352 | padding-top: 0; 353 | margin-bottom: 1.45rem; 354 | list-style-position: outside; 355 | list-style-image: none; 356 | } 357 | dl { 358 | margin-left: 0; 359 | margin-right: 0; 360 | margin-top: 0; 361 | padding-bottom: 0; 362 | padding-left: 0; 363 | padding-right: 0; 364 | padding-top: 0; 365 | margin-bottom: 1.45rem; 366 | } 367 | dd { 368 | margin-left: 0; 369 | margin-right: 0; 370 | margin-top: 0; 371 | padding-bottom: 0; 372 | padding-left: 0; 373 | padding-right: 0; 374 | padding-top: 0; 375 | margin-bottom: 1.45rem; 376 | } 377 | p { 378 | margin-left: 0; 379 | margin-right: 0; 380 | margin-top: 0; 381 | padding-bottom: 0; 382 | padding-left: 0; 383 | padding-right: 0; 384 | padding-top: 0; 385 | margin-bottom: 1.45rem; 386 | } 387 | figure { 388 | margin-left: 0; 389 | margin-right: 0; 390 | margin-top: 0; 391 | padding-bottom: 0; 392 | padding-left: 0; 393 | padding-right: 0; 394 | padding-top: 0; 395 | margin-bottom: 1.45rem; 396 | } 397 | pre { 398 | margin-left: 0; 399 | margin-right: 0; 400 | margin-top: 0; 401 | padding-bottom: 0; 402 | padding-left: 0; 403 | padding-right: 0; 404 | padding-top: 0; 405 | margin-bottom: 1.45rem; 406 | font-size: 0.85rem; 407 | line-height: 1.42; 408 | background: hsla(0, 0%, 0%, 0.04); 409 | border-radius: 3px; 410 | overflow: auto; 411 | word-wrap: normal; 412 | padding: 1.45rem; 413 | } 414 | table { 415 | margin-left: 0; 416 | margin-right: 0; 417 | margin-top: 0; 418 | padding-bottom: 0; 419 | padding-left: 0; 420 | padding-right: 0; 421 | padding-top: 0; 422 | margin-bottom: 1.45rem; 423 | font-size: 1rem; 424 | line-height: 1.45rem; 425 | border-collapse: collapse; 426 | width: 100%; 427 | } 428 | fieldset { 429 | margin-left: 0; 430 | margin-right: 0; 431 | margin-top: 0; 432 | padding-bottom: 0; 433 | padding-left: 0; 434 | padding-right: 0; 435 | padding-top: 0; 436 | margin-bottom: 1.45rem; 437 | } 438 | blockquote { 439 | margin-left: 1.45rem; 440 | margin-right: 1.45rem; 441 | margin-top: 0; 442 | padding-bottom: 0; 443 | padding-left: 0; 444 | padding-right: 0; 445 | padding-top: 0; 446 | margin-bottom: 1.45rem; 447 | } 448 | form { 449 | margin-left: 0; 450 | margin-right: 0; 451 | margin-top: 0; 452 | padding-bottom: 0; 453 | padding-left: 0; 454 | padding-right: 0; 455 | padding-top: 0; 456 | margin-bottom: 1.45rem; 457 | } 458 | noscript { 459 | margin-left: 0; 460 | margin-right: 0; 461 | margin-top: 0; 462 | padding-bottom: 0; 463 | padding-left: 0; 464 | padding-right: 0; 465 | padding-top: 0; 466 | margin-bottom: 1.45rem; 467 | } 468 | iframe { 469 | margin-left: 0; 470 | margin-right: 0; 471 | margin-top: 0; 472 | padding-bottom: 0; 473 | padding-left: 0; 474 | padding-right: 0; 475 | padding-top: 0; 476 | margin-bottom: 1.45rem; 477 | } 478 | hr { 479 | margin-left: 0; 480 | margin-right: 0; 481 | margin-top: 0; 482 | padding-bottom: 0; 483 | padding-left: 0; 484 | padding-right: 0; 485 | padding-top: 0; 486 | margin-bottom: calc(1.45rem - 1px); 487 | background: hsla(0, 0%, 0%, 0.2); 488 | border: none; 489 | height: 1px; 490 | } 491 | address { 492 | margin-left: 0; 493 | margin-right: 0; 494 | margin-top: 0; 495 | padding-bottom: 0; 496 | padding-left: 0; 497 | padding-right: 0; 498 | padding-top: 0; 499 | margin-bottom: 1.45rem; 500 | } 501 | b { 502 | font-weight: bold; 503 | } 504 | strong { 505 | font-weight: bold; 506 | } 507 | dt { 508 | font-weight: bold; 509 | } 510 | th { 511 | font-weight: bold; 512 | } 513 | li { 514 | margin-bottom: calc(1.45rem / 2); 515 | } 516 | ol li { 517 | padding-left: 0; 518 | } 519 | ul li { 520 | padding-left: 0; 521 | } 522 | li > ol { 523 | margin-left: 1.45rem; 524 | margin-bottom: calc(1.45rem / 2); 525 | margin-top: calc(1.45rem / 2); 526 | } 527 | li > ul { 528 | margin-left: 1.45rem; 529 | margin-bottom: calc(1.45rem / 2); 530 | margin-top: calc(1.45rem / 2); 531 | } 532 | blockquote *:last-child { 533 | margin-bottom: 0; 534 | } 535 | li *:last-child { 536 | margin-bottom: 0; 537 | } 538 | p *:last-child { 539 | margin-bottom: 0; 540 | } 541 | li > p { 542 | margin-bottom: calc(1.45rem / 2); 543 | } 544 | code { 545 | font-size: 0.85rem; 546 | line-height: 1.45rem; 547 | } 548 | kbd { 549 | font-size: 0.85rem; 550 | line-height: 1.45rem; 551 | } 552 | samp { 553 | font-size: 0.85rem; 554 | line-height: 1.45rem; 555 | } 556 | abbr { 557 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 558 | cursor: help; 559 | } 560 | acronym { 561 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 562 | cursor: help; 563 | } 564 | abbr[title] { 565 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 566 | cursor: help; 567 | text-decoration: none; 568 | } 569 | thead { 570 | text-align: left; 571 | } 572 | td, 573 | th { 574 | text-align: left; 575 | border-bottom: 1px solid hsla(0, 0%, 0%, 0.12); 576 | font-feature-settings: "tnum"; 577 | -moz-font-feature-settings: "tnum"; 578 | -ms-font-feature-settings: "tnum"; 579 | -webkit-font-feature-settings: "tnum"; 580 | padding-left: 0.96667rem; 581 | padding-right: 0.96667rem; 582 | padding-top: 0.725rem; 583 | padding-bottom: calc(0.725rem - 1px); 584 | } 585 | th:first-child, 586 | td:first-child { 587 | padding-left: 0; 588 | } 589 | th:last-child, 590 | td:last-child { 591 | padding-right: 0; 592 | } 593 | tt, 594 | code { 595 | background-color: hsla(0, 0%, 0%, 0.04); 596 | border-radius: 3px; 597 | font-family: "SFMono-Regular", Consolas, "Roboto Mono", "Droid Sans Mono", 598 | "Liberation Mono", Menlo, Courier, monospace; 599 | padding: 0; 600 | padding-top: 0.2em; 601 | padding-bottom: 0.2em; 602 | } 603 | pre code { 604 | background: none; 605 | line-height: 1.42; 606 | } 607 | code:before, 608 | code:after, 609 | tt:before, 610 | tt:after { 611 | letter-spacing: -0.2em; 612 | content: " "; 613 | } 614 | pre code:before, 615 | pre code:after, 616 | pre tt:before, 617 | pre tt:after { 618 | content: ""; 619 | } 620 | @media only screen and (max-width: 480px) { 621 | html { 622 | font-size: 100%; 623 | } 624 | } 625 | -------------------------------------------------------------------------------- /src/layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'gatsby' 3 | import { Helmet } from 'react-helmet' 4 | 5 | import './css/typography.css' 6 | import './css/styles.css' 7 | 8 | export default function Template({ children }) { 9 | return ( 10 |
11 | 15 |
21 |
28 |

29 | 36 | Gatsby + Netlify Forms 37 | 38 |

39 |
40 |
41 |
49 | {children} 50 |
51 |
52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Layout from '../layout' 3 | 4 | export default () => ( 5 | 6 |

NOT FOUND

7 |

You just hit a route that doesn't exist... the sadness.

8 |
9 | ) 10 | -------------------------------------------------------------------------------- /src/pages/contact.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { navigate } from 'gatsby-link' 3 | import Layout from '../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 function Contact() { 12 | const [state, setState] = React.useState({}) 13 | 14 | const handleChange = (e) => { 15 | setState({ ...state, [e.target.name]: e.target.value }) 16 | } 17 | 18 | const handleSubmit = (e) => { 19 | e.preventDefault() 20 | const form = e.target 21 | fetch('/', { 22 | method: 'POST', 23 | headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, 24 | body: encode({ 25 | 'form-name': form.getAttribute('name'), 26 | ...state, 27 | }), 28 | }) 29 | .then(() => navigate(form.getAttribute('action'))) 30 | .catch((error) => alert(error)) 31 | } 32 | 33 | return ( 34 | 35 |

Contact

36 |
44 | {/* The `form-name` hidden field is required to support form submissions without JavaScript */} 45 | 46 | 51 |

52 | 57 |

58 |

59 | 64 |

65 |

66 |