Repository: imorente/gatsby-netlify-form-example
Branch: master
Commit: fc08d5af1fa4
Files: 15
Total size: 26.2 KB
Directory structure:
gitextract_wse6q8pa/
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── gatsby-config.js
├── package.json
└── src/
├── css/
│ └── typography.css
├── html.js
├── layouts/
│ └── index.js
└── pages/
├── 404.js
├── contact.js
├── file-upload.js
├── index.js
├── recaptcha.js
└── thanks.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
.gatsby-context.js
.sass-cache/
public/
.cache/
.DS_Store
# Environment variables
.env.development
================================================
FILE: .travis.yml
================================================
# back to language cpp to try to bypass osx node failure
language: cpp
sudo: false
env:
- export NODE_VERSION="0.12"
- export NODE_VERSION="4"
- export NODE_VERSION="5"
os:
- linux
- osx
# pre-install to bring in the correct version of node via nvm
before_install:
- git submodule update --init --recursive
- git clone https://github.com/creationix/nvm.git ./.nvm
- source ./.nvm/nvm.sh
- nvm install $NODE_VERSION
- nvm use $NODE_VERSION
- npm config set python `which python`
- if [ $TRAVIS_OS_NAME == "linux" ]; then
export CC="gcc-4.8";
export CXX="g++-4.8";
export LINK="gcc-4.8";
export LINKXX="g++-4.8";
fi
- gcc --version
- g++ --version
# node 4 depends on gcc 4.8
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
- gcc-4.8
# script needed to test, because defaults don't work on osx
script:
- npm install
- npm run lint
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2015 gatsbyjs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Integrating Netlify Form Handling in Gatsby
Example for integrating a basic contact form with Netlify’s form handling feature (based on the [default Gatsby starter](https://github.com/gatsbyjs/gatsby-starter-default))
Demo: https://gatsby-form-example.netlify.com
## Deploy
[](https://app.netlify.com/start/deploy?repository=https://github.com/imorente/gatsby-netlify-form-example)
## reCAPTCHA
This example site uses [react-google-recaptcha](https://github.com/dozoisch/react-google-recaptcha) to render the reCAPTCHA widget.
To make the reCAPTCHA example work in your own copy of this site, you’ll need to do the following:
1. [Sign up for a reCAPTCHA API key pair](http://www.google.com/recaptcha/admin) for your site.
2. [Log in to your Netlify account](https://app.netlify.com), and add the following
environment variables to your site’s Settings > Build & deploy > Build environment variables:
- `SITE_RECAPTCHA_KEY` with your reCAPTCHA site key.
- `SITE_RECAPTCHA_SECRET` with your reCAPTCHA secret key.
**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.
3. Change the build command for your site to
```
echo SITE_RECAPTCHA_KEY=$SITE_RECAPTCHA_KEY >> .env.production && gatsby build
```
This will make the SITE_RECAPTCHA_KEY available to the Gatsby build in production.
To see the reCAPTCHA widget locally, add `SITE_RECAPTCHA_KEY=your-reCAPTCHA-API-site-key`
to your local [.env.development](https://www.gatsbyjs.org/docs/environment-variables/) file.
## Troubleshooting
### Forms stop working after upgrading to Gatsby v2
This can be caused by the offline-plugin. [Workaround](https://github.com/gatsbyjs/gatsby/issues/7997#issuecomment-419749232) is to use `?no-cache=1` in the POST url to prevent the service worker from handling form submissions (Thanks to [@phmu_office](https://twitter.com/phmu_office/status/1047810173417472000) for the heads up ✨)
================================================
FILE: gatsby-config.js
================================================
module.exports = {
siteMetadata: {
title: `Gatsby Netlify Form Integration`
},
plugins: [`gatsby-plugin-react-helmet`]
};
================================================
FILE: package.json
================================================
{
"name": "gatsby-starter-default",
"description": "Gatsby default starter",
"version": "1.0.0",
"author": "Kyle Mathews <mathews.kyle@gmail.com>",
"dependencies": {
"gatsby": "^1.9.261",
"gatsby-link": "^1.0.1",
"gatsby-plugin-react-helmet": "^1.0.1",
"react-async-script": "^0.9.1",
"react-google-recaptcha": "^0.11.1"
},
"devDependencies": {
"dotenv": "^5.0.1",
"gh-pages": "^0.12.0"
},
"keywords": [
"gatsby"
],
"license": "MIT",
"main": "n/a",
"scripts": {
"build": "gatsby build",
"deploy": "gatsby build --prefix-paths && gh-pages -d public",
"develop": "gatsby develop",
"format": "prettier --trailing-comma es5 --no-semi --single-quote --write \"pages/*.js\" \"utils/*.js\" \"wrappers/*.js\" \"html.js\"",
"test": "echo \"Error: no test specified\" && exit 1"
}
}
================================================
FILE: src/css/typography.css
================================================
html {
font-family: sans-serif;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
body {
margin: 0;
}
article,
aside,
details,
figcaption,
figure,
footer,
header,
main,
menu,
nav,
section,
summary {
display: block;
}
audio,
canvas,
progress,
video {
display: inline-block;
}
audio:not([controls]) {
display: none;
height: 0;
}
progress {
vertical-align: baseline;
}
[hidden],
template {
display: none;
}
a {
background-color: transparent;
-webkit-text-decoration-skip: objects;
}
a:active,
a:hover {
outline-width: 0;
}
abbr[title] {
border-bottom: none;
text-decoration: underline;
text-decoration: underline dotted;
}
b,
strong {
font-weight: inherit;
font-weight: bolder;
}
dfn {
font-style: italic;
}
h1 {
font-size: 2em;
margin: .67em 0;
}
mark {
background-color: #ff0;
color: #000;
}
small {
font-size: 80%;
}
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -.25em;
}
sup {
top: -.5em;
}
img {
border-style: none;
}
svg:not(:root) {
overflow: hidden;
}
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
figure {
margin: 1em 40px;
}
hr {
box-sizing: content-box;
height: 0;
overflow: visible;
}
button,
input,
optgroup,
select,
textarea {
font: inherit;
margin: 0;
}
optgroup {
font-weight: 700;
}
button,
input {
overflow: visible;
}
button,
select {
text-transform: none;
}
[type=reset],
[type=submit],
button,
html [type=button] {
-webkit-appearance: button;
}
[type=button]::-moz-focus-inner,
[type=reset]::-moz-focus-inner,
[type=submit]::-moz-focus-inner,
button::-moz-focus-inner {
border-style: none;
padding: 0;
}
[type=button]:-moz-focusring,
[type=reset]:-moz-focusring,
[type=submit]:-moz-focusring,
button:-moz-focusring {
outline: 1px dotted ButtonText;
}
fieldset {
border: 1px solid silver;
margin: 0 2px;
padding: .35em .625em .75em;
}
legend {
box-sizing: border-box;
color: inherit;
display: table;
max-width: 100%;
padding: 0;
white-space: normal;
}
textarea {
overflow: auto;
}
[type=checkbox],
[type=radio] {
box-sizing: border-box;
padding: 0;
}
[type=number]::-webkit-inner-spin-button,
[type=number]::-webkit-outer-spin-button {
height: auto;
}
[type=search] {
-webkit-appearance: textfield;
outline-offset: -2px;
}
[type=search]::-webkit-search-cancel-button,
[type=search]::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-input-placeholder {
color: inherit;
opacity: .54;
}
::-webkit-file-upload-button {
-webkit-appearance: button;
font: inherit;
}
html {
font: 112.5%/1.45em georgia, serif;
box-sizing: border-box;
overflow-y: scroll;
}
* {
box-sizing: inherit;
}
*:before {
box-sizing: inherit;
}
*:after {
box-sizing: inherit;
}
body {
color: hsla(0, 0%, 0%, 0.8);
font-family: georgia, serif;
font-weight: normal;
word-wrap: break-word;
font-kerning: normal;
-moz-font-feature-settings: "kern", "liga", "clig", "calt";
-ms-font-feature-settings: "kern", "liga", "clig", "calt";
-webkit-font-feature-settings: "kern", "liga", "clig", "calt";
font-feature-settings: "kern", "liga", "clig", "calt";
}
img {
max-width: 100%;
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
h1 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-weight: bold;
text-rendering: optimizeLegibility;
font-size: 2.25rem;
line-height: 2.9rem;
}
h2 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-weight: bold;
text-rendering: optimizeLegibility;
font-size: 1.62671rem;
line-height: 2.175rem;
}
h3 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-weight: bold;
text-rendering: optimizeLegibility;
font-size: 1.38316rem;
line-height: 2.175rem;
}
h4 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-weight: bold;
text-rendering: optimizeLegibility;
font-size: 1rem;
line-height: 1.45rem;
}
h5 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-weight: bold;
text-rendering: optimizeLegibility;
font-size: 0.85028rem;
line-height: 1.45rem;
}
h6 {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
color: inherit;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
font-weight: bold;
text-rendering: optimizeLegibility;
font-size: 0.78405rem;
line-height: 1.45rem;
}
hgroup {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
ul {
margin-left: 1.45rem;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
list-style-position: outside;
list-style-image: none;
}
ol {
margin-left: 1.45rem;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
list-style-position: outside;
list-style-image: none;
}
dl {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
dd {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
p {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
figure {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
pre {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
font-size: 0.85rem;
line-height: 1.42;
background: hsla(0, 0%, 0%, 0.04);
border-radius: 3px;
overflow: auto;
word-wrap: normal;
padding: 1.45rem;
}
table {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
font-size: 1rem;
line-height: 1.45rem;
border-collapse: collapse;
width: 100%;
}
fieldset {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
blockquote {
margin-left: 1.45rem;
margin-right: 1.45rem;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
form {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
noscript {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
iframe {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
hr {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: calc(1.45rem - 1px);
background: hsla(0, 0%, 0%, 0.2);
border: none;
height: 1px;
}
address {
margin-left: 0;
margin-right: 0;
margin-top: 0;
padding-bottom: 0;
padding-left: 0;
padding-right: 0;
padding-top: 0;
margin-bottom: 1.45rem;
}
b {
font-weight: bold;
}
strong {
font-weight: bold;
}
dt {
font-weight: bold;
}
th {
font-weight: bold;
}
li {
margin-bottom: calc(1.45rem / 2);
}
ol li {
padding-left: 0;
}
ul li {
padding-left: 0;
}
li > ol {
margin-left: 1.45rem;
margin-bottom: calc(1.45rem / 2);
margin-top: calc(1.45rem / 2);
}
li > ul {
margin-left: 1.45rem;
margin-bottom: calc(1.45rem / 2);
margin-top: calc(1.45rem / 2);
}
blockquote *:last-child {
margin-bottom: 0;
}
li *:last-child {
margin-bottom: 0;
}
p *:last-child {
margin-bottom: 0;
}
li > p {
margin-bottom: calc(1.45rem / 2);
}
code {
font-size: 0.85rem;
line-height: 1.45rem;
}
kbd {
font-size: 0.85rem;
line-height: 1.45rem;
}
samp {
font-size: 0.85rem;
line-height: 1.45rem;
}
abbr {
border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5);
cursor: help;
}
acronym {
border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5);
cursor: help;
}
abbr[title] {
border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5);
cursor: help;
text-decoration: none;
}
thead {
text-align: left;
}
td,
th {
text-align: left;
border-bottom: 1px solid hsla(0, 0%, 0%, 0.12);
font-feature-settings: "tnum";
-moz-font-feature-settings: "tnum";
-ms-font-feature-settings: "tnum";
-webkit-font-feature-settings: "tnum";
padding-left: 0.96667rem;
padding-right: 0.96667rem;
padding-top: 0.725rem;
padding-bottom: calc(0.725rem - 1px);
}
th:first-child,
td:first-child {
padding-left: 0;
}
th:last-child,
td:last-child {
padding-right: 0;
}
tt,
code {
background-color: hsla(0, 0%, 0%, 0.04);
border-radius: 3px;
font-family: "SFMono-Regular", Consolas, "Roboto Mono", "Droid Sans Mono",
"Liberation Mono", Menlo, Courier, monospace;
padding: 0;
padding-top: 0.2em;
padding-bottom: 0.2em;
}
pre code {
background: none;
line-height: 1.42;
}
code:before,
code:after,
tt:before,
tt:after {
letter-spacing: -0.2em;
content: " ";
}
pre code:before,
pre code:after,
pre tt:before,
pre tt:after {
content: "";
}
@media only screen and (max-width: 480px) {
html {
font-size: 100%;
}
}
================================================
FILE: src/html.js
================================================
import React from "react"
import PropTypes from "prop-types"
const BUILD_TIME = new Date().getTime()
export default class HTML extends React.Component {
static propTypes = {
body: PropTypes.string,
}
render() {
let css
if (process.env.NODE_ENV === "production") {
css = (
<style
dangerouslySetInnerHTML={{
__html: require("!raw!../public/styles.css"),
}}
/>
)
}
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
/>
{this.props.headComponents}
{css}
</head>
<body>
<div
id="___gatsby"
dangerouslySetInnerHTML={{ __html: this.props.body }}
/>
{this.props.postBodyComponents}
</body>
</html>
)
}
}
================================================
FILE: src/layouts/index.js
================================================
import React from "react"
import PropTypes from "prop-types"
import Link from "gatsby-link"
import Helmet from "react-helmet"
import "../css/typography.css"
export default class Template extends React.Component {
static propTypes = {
children: PropTypes.func,
}
render() {
return (
<div>
<Helmet
title="Gatsby Default Starter"
meta={[
{ name: "description", content: "Sample" },
{ name: "keywords", content: "sample, something" },
]}
/>
<div
style={{
background: `rebeccapurple`,
marginBottom: `1.45rem`,
}}
>
<div
style={{
margin: `0 auto`,
maxWidth: 960,
padding: `1.45rem 1.0875rem`,
}}
>
<h1 style={{ margin: 0 }}>
<Link
to="/"
style={{
color: "white",
textDecoration: "none",
}}
>
Gatsby
</Link>
</h1>
</div>
</div>
<div
style={{
margin: `0 auto`,
maxWidth: 960,
padding: `0px 1.0875rem 1.45rem`,
paddingTop: 0,
}}
>
{this.props.children()}
</div>
</div>
)
}
}
================================================
FILE: src/pages/404.js
================================================
import React from "react"
export default () =>
<div>
<h1>NOT FOUND</h1>
<p>You just hit a route that doesn't exist... the sadness.</p>
</div>
================================================
FILE: src/pages/contact.js
================================================
import React from "react";
import { navigateTo } from "gatsby-link";
function encode(data) {
return Object.keys(data)
.map(key => encodeURIComponent(key) + "=" + encodeURIComponent(data[key]))
.join("&");
}
export default class Contact extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
handleChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
handleSubmit = e => {
e.preventDefault();
const form = e.target;
fetch("/", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: encode({
"form-name": form.getAttribute("name"),
...this.state
})
})
.then(() => navigateTo(form.getAttribute("action")))
.catch(error => alert(error));
};
render() {
return (
<div>
<h1>Contact</h1>
<form
name="contact"
method="post"
action="/thanks/"
data-netlify="true"
data-netlify-honeypot="bot-field"
onSubmit={this.handleSubmit}
>
{/* The `form-name` hidden field is required to support form submissions without JavaScript */}
<input type="hidden" name="form-name" value="contact" />
<p hidden>
<label>
Don’t fill this out:{" "}
<input name="bot-field" onChange={this.handleChange} />
</label>
</p>
<p>
<label>
Your name:<br />
<input type="text" name="name" onChange={this.handleChange} />
</label>
</p>
<p>
<label>
Your email:<br />
<input type="email" name="email" onChange={this.handleChange} />
</label>
</p>
<p>
<label>
Message:<br />
<textarea name="message" onChange={this.handleChange} />
</label>
</p>
<p>
<button type="submit">Send</button>
</p>
</form>
</div>
);
}
}
================================================
FILE: src/pages/file-upload.js
================================================
import React from "react";
import Link from "gatsby-link";
import Helmet from "react-helmet";
import { navigateTo } from "gatsby-link";
function encode(data) {
const formData = new FormData();
for (const key of Object.keys(data)) {
formData.append(key, data[key]);
}
return formData;
}
export default class Contact extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
handleChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
handleAttachment = e => {
this.setState({ [e.target.name]: e.target.files[0] });
};
handleSubmit = e => {
e.preventDefault();
const form = e.target;
fetch("/", {
method: "POST",
body: encode({
"form-name": form.getAttribute("name"),
...this.state
})
})
.then(() => navigateTo(form.getAttribute("action")))
.catch(error => alert(error));
};
render() {
return (
<div>
<h1>File Upload</h1>
<form
name="file-upload"
method="post"
action="/thanks/"
data-netlify="true"
data-netlify-honeypot="bot-field"
onSubmit={this.handleSubmit}
>
{/* The `form-name` hidden field is required to support form submissions without JavaScript */}
<input type="hidden" name="form-name" value="file-upload" />
<p hidden>
<label>
Don’t fill this out:{" "}
<input name="bot-field" onChange={this.handleChange} />
</label>
</p>
<p>
<label>
Your name:<br />
<input type="text" name="name" onChange={this.handleChange} />
</label>
</p>
<p>
<label>
File:<br />
<input
type="file"
name="attachment"
onChange={this.handleAttachment}
/>
</label>
</p>
<p>
<button type="submit">Send</button>
</p>
</form>
</div>
);
}
}
================================================
FILE: src/pages/index.js
================================================
import React from "react";
import Link from "gatsby-link";
import Helmet from "react-helmet";
export default class Index extends React.Component {
render() {
return (
<div>
<h1>Hi people</h1>
<p>
This is an example site integrating Netlify’s form handling with Gatsby
</p>
<ul>
<li><Link to="/contact/">Basic contact form</Link></li>
<li><Link to="/file-upload/">Form with file upload</Link></li>
<li><Link to="/recaptcha/">Form with reCAPTCHA 2</Link></li>
</ul>
</div>
);
}
}
================================================
FILE: src/pages/recaptcha.js
================================================
import React from "react";
import { navigateTo } from "gatsby-link";
import Recaptcha from "react-google-recaptcha";
const RECAPTCHA_KEY = process.env.SITE_RECAPTCHA_KEY;
function encode(data) {
return Object.keys(data)
.map(key => encodeURIComponent(key) + "=" + encodeURIComponent(data[key]))
.join("&");
}
export default class Contact extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
handleChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
handleRecaptcha = value => {
this.setState({ "g-recaptcha-response": value });
};
handleSubmit = e => {
e.preventDefault();
const form = e.target;
fetch("/", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: encode({
"form-name": form.getAttribute("name"),
...this.state
})
})
.then(() => navigateTo(form.getAttribute("action")))
.catch(error => alert(error));
};
render() {
return (
<div>
<h1>reCAPTCHA 2</h1>
<form
name="contact-recaptcha"
method="post"
action="/thanks/"
data-netlify="true"
data-netlify-recaptcha="true"
onSubmit={this.handleSubmit}
>
<noscript>
<p>This form won’t work with Javascript disabled</p>
</noscript>
<p>
<label>
Your name:<br />
<input type="text" name="name" onChange={this.handleChange} />
</label>
</p>
<p>
<label>
Your email:<br />
<input type="email" name="email" onChange={this.handleChange} />
</label>
</p>
<p>
<label>
Message:<br />
<textarea name="message" onChange={this.handleChange} />
</label>
</p>
<Recaptcha
ref="recaptcha"
sitekey={RECAPTCHA_KEY}
onChange={this.handleRecaptcha}
/>
<p>
<button type="submit">Send</button>
</p>
</form>
</div>
);
}
}
================================================
FILE: src/pages/thanks.js
================================================
import React from "react";
export default () => (
<div>
<h1>Thank you!</h1>
<p>This is a custom thank you page for form submissions</p>
</div>
);
gitextract_wse6q8pa/
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── gatsby-config.js
├── package.json
└── src/
├── css/
│ └── typography.css
├── html.js
├── layouts/
│ └── index.js
└── pages/
├── 404.js
├── contact.js
├── file-upload.js
├── index.js
├── recaptcha.js
└── thanks.js
SYMBOL INDEX (20 symbols across 6 files)
FILE: src/html.js
constant BUILD_TIME (line 4) | const BUILD_TIME = new Date().getTime()
class HTML (line 6) | class HTML extends React.Component {
method render (line 11) | render() {
FILE: src/layouts/index.js
class Template (line 8) | class Template extends React.Component {
method render (line 13) | render() {
FILE: src/pages/contact.js
function encode (line 4) | function encode(data) {
class Contact (line 10) | class Contact extends React.Component {
method constructor (line 11) | constructor(props) {
method render (line 35) | render() {
FILE: src/pages/file-upload.js
function encode (line 6) | function encode(data) {
class Contact (line 16) | class Contact extends React.Component {
method constructor (line 17) | constructor(props) {
method render (line 44) | render() {
FILE: src/pages/index.js
class Index (line 5) | class Index extends React.Component {
method render (line 6) | render() {
FILE: src/pages/recaptcha.js
constant RECAPTCHA_KEY (line 5) | const RECAPTCHA_KEY = process.env.SITE_RECAPTCHA_KEY;
function encode (line 7) | function encode(data) {
class Contact (line 13) | class Contact extends React.Component {
method constructor (line 14) | constructor(props) {
method render (line 42) | render() {
Condensed preview — 15 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (29K chars).
[
{
"path": ".gitignore",
"chars": 626,
"preview": "# Logs\nlogs\n*.log\n\n# Runtime data\npids\n*.pid\n*.seed\n\n# Directory for instrumented libs generated by jscoverage/JSCover\nl"
},
{
"path": ".travis.yml",
"chars": 931,
"preview": "# back to language cpp to try to bypass osx node failure\nlanguage: cpp\nsudo: false\nenv:\n - export NODE_VERSION=\"0.12\"\n "
},
{
"path": "LICENSE",
"chars": 1076,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2015 gatsbyjs\n\nPermission is hereby granted, free of charge, to any person obtainin"
},
{
"path": "README.md",
"chars": 2220,
"preview": "# Integrating Netlify Form Handling in Gatsby\n\nExample for integrating a basic contact form with Netlify’s form handling"
},
{
"path": "gatsby-config.js",
"chars": 133,
"preview": "\nmodule.exports = {\n siteMetadata: {\n title: `Gatsby Netlify Form Integration`\n },\n plugins: [`gatsby-plugin-react"
},
{
"path": "package.json",
"chars": 853,
"preview": "{\n \"name\": \"gatsby-starter-default\",\n \"description\": \"Gatsby default starter\",\n \"version\": \"1.0.0\",\n \"author\": \"Kyle"
},
{
"path": "src/css/typography.css",
"chars": 11269,
"preview": "html {\n font-family: sans-serif;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n}\nbody {\n margin: 0;\n"
},
{
"path": "src/html.js",
"chars": 1006,
"preview": "import React from \"react\"\nimport PropTypes from \"prop-types\"\n\nconst BUILD_TIME = new Date().getTime()\n\nexport default cl"
},
{
"path": "src/layouts/index.js",
"chars": 1403,
"preview": "import React from \"react\"\nimport PropTypes from \"prop-types\"\nimport Link from \"gatsby-link\"\nimport Helmet from \"react-he"
},
{
"path": "src/pages/404.js",
"chars": 155,
"preview": "import React from \"react\"\n\nexport default () =>\n <div>\n <h1>NOT FOUND</h1>\n <p>You just hit a route that doesn't "
},
{
"path": "src/pages/contact.js",
"chars": 2107,
"preview": "import React from \"react\";\nimport { navigateTo } from \"gatsby-link\";\n\nfunction encode(data) {\n return Object.keys(data)"
},
{
"path": "src/pages/file-upload.js",
"chars": 2117,
"preview": "import React from \"react\";\nimport Link from \"gatsby-link\";\nimport Helmet from \"react-helmet\";\nimport { navigateTo } from"
},
{
"path": "src/pages/index.js",
"chars": 583,
"preview": "import React from \"react\";\nimport Link from \"gatsby-link\";\nimport Helmet from \"react-helmet\";\n\nexport default class Inde"
},
{
"path": "src/pages/recaptcha.js",
"chars": 2201,
"preview": "import React from \"react\";\nimport { navigateTo } from \"gatsby-link\";\nimport Recaptcha from \"react-google-recaptcha\";\n\nco"
},
{
"path": "src/pages/thanks.js",
"chars": 159,
"preview": "import React from \"react\";\n\nexport default () => (\n <div>\n <h1>Thank you!</h1>\n <p>This is a custom thank you pag"
}
]
About this extraction
This page contains the full source code of the imorente/gatsby-netlify-form-example GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 15 files (26.2 KB), approximately 8.0k tokens, and a symbol index with 20 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.