Skip to content

Commit 53db002

Browse files
committed
add newsletter sign up
1 parent 623f6ab commit 53db002

File tree

5 files changed

+267
-52
lines changed

5 files changed

+267
-52
lines changed

.env

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11

22
RAZZLE_ONEGRAPH_APP_ID="570a3d6b-6ff3-4b7a-9b0d-fe4cf6384388"
3+
RAZZLE_ENABLE_MAILCHIMP_SIGNUP=true
34
REPOSITORY_FIXED_VARIABLES='{"repoName": "onegraph-changelog", "repoOwner": "onegraph"}'

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,6 @@ The project can be deployed with Netlify and Netlify functions. The config lives
103103
To deploy
104104

105105
```
106-
yarn build
107-
yarn build:netlify-functions
108106
yarn deploy:netlify
109107
```
110108

src/App.js

Lines changed: 79 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,20 @@ import Link from './PreloadLink';
1313
import idx from 'idx';
1414
import {NotificationContainer} from './Notifications';
1515
import OneGraphLogo from './oneGraphLogo';
16-
import {Grommet, Grid, Box, Heading, Text, Anchor} from 'grommet';
16+
import {
17+
Grommet,
18+
Grid,
19+
Box,
20+
Heading,
21+
Text,
22+
Anchor,
23+
ResponsiveContext,
24+
} from 'grommet';
1725
import {StatusCritical} from 'grommet-icons';
1826
import ScrollMemory from 'react-router-scroll-memory';
1927
import {matchPath} from 'react-router-dom';
2028
import UserContext from './UserContext';
29+
import NewsletterSignup from './NewsletterSignup';
2130

2231
import type {App_ViewerQueryResponse} from './__generated__/App_Query.graphql';
2332
import type {Environment} from 'relay-runtime';
@@ -218,54 +227,75 @@ export default class App extends React.Component<
218227
}}>
219228
<NotificationContainer>
220229
<Grommet theme={theme}>
221-
<Grid
222-
fill
223-
rows={['auto', 'flex']}
224-
columns={['flex']}
225-
areas={[
226-
{name: 'header', start: [0, 0], end: [1, 0]},
227-
{name: 'main', start: [0, 1], end: [1, 1]},
228-
]}>
229-
<Box
230-
gridArea="header"
231-
direction="row"
232-
align="center"
233-
justify="between"
234-
pad={{horizontal: 'medium', vertical: 'medium'}}
235-
wrap={true}>
236-
<Box align="center" direction="row">
237-
<OneGraphLogo width="96px" height="96px" />{' '}
238-
<Heading level={2}>
239-
<Link style={{color: 'inherit'}} to="/">
240-
OneGraph Product Updates
241-
</Link>
242-
</Heading>
243-
</Box>
244-
<Anchor href="https://onegraph.com">
245-
<Text size="small">Learn more about OneGraph</Text>
246-
</Anchor>
247-
</Box>
248-
<Box gridArea="main">
249-
<ScrollMemory />
250-
<Switch>
251-
{routes.map((routeConfig, i) => (
252-
<Route
253-
key={i}
254-
path={routeConfig.path}
255-
exact={routeConfig.exact}
256-
strict={routeConfig.strict}
257-
render={props => (
258-
<RenderRoute
259-
environment={this.props.environment}
260-
match={props.match}
261-
routeConfig={routeConfig}
262-
/>
263-
)}
264-
/>
265-
))}
266-
</Switch>
267-
</Box>
268-
</Grid>
230+
<ResponsiveContext.Consumer>
231+
{size => {
232+
const small = size === 'small';
233+
return (
234+
<Grid
235+
fill
236+
rows={['auto', 'flex']}
237+
columns={small ? ['flex'] : ['flex', 'auto']}
238+
areas={
239+
small
240+
? [
241+
{name: 'header', start: [0, 0], end: [1, 0]},
242+
{name: 'main', start: [0, 1], end: [1, 1]},
243+
]
244+
: [
245+
{name: 'header', start: [0, 0], end: [1, 0]},
246+
{name: 'main', start: [0, 1], end: [1, 1]},
247+
{name: 'sidebar', start: [1, 1], end: [1, 1]},
248+
]
249+
}>
250+
<Box
251+
gridArea="header"
252+
direction="row"
253+
align="center"
254+
justify="between"
255+
pad={{horizontal: 'medium', vertical: 'medium'}}
256+
wrap={true}>
257+
<Box align="center" direction="row">
258+
<OneGraphLogo width="96px" height="96px" />{' '}
259+
<Heading level={2}>
260+
<Link style={{color: 'inherit'}} to="/">
261+
OneGraph Product Updates
262+
</Link>
263+
</Heading>
264+
</Box>
265+
<Anchor href="https://onegraph.com">
266+
<Text size="small">Learn more about OneGraph</Text>
267+
</Anchor>
268+
</Box>
269+
{small ||
270+
!process.env.RAZZLE_ENABLE_MAILCHIMP_SIGNUP ? null : (
271+
<Box gridArea="sidebar" pad="medium" width="medium">
272+
<NewsletterSignup />
273+
</Box>
274+
)}
275+
<Box gridArea="main">
276+
<ScrollMemory />
277+
<Switch>
278+
{routes.map((routeConfig, i) => (
279+
<Route
280+
key={i}
281+
path={routeConfig.path}
282+
exact={routeConfig.exact}
283+
strict={routeConfig.strict}
284+
render={props => (
285+
<RenderRoute
286+
environment={this.props.environment}
287+
match={props.match}
288+
routeConfig={routeConfig}
289+
/>
290+
)}
291+
/>
292+
))}
293+
</Switch>
294+
</Box>
295+
</Grid>
296+
);
297+
}}
298+
</ResponsiveContext.Consumer>
269299
</Grommet>
270300
</NotificationContainer>
271301
</UserContext.Provider>

src/NewsletterSignup.js

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// @flow
2+
3+
import React from 'react';
4+
import idx from 'idx';
5+
import {Box, Heading, Form, FormField, Button, TextInput, Text} from 'grommet';
6+
7+
// Create your own signup from the Persisted Queries tab on OneGraph
8+
// 1. create a token with mailchimp auth from the "Server-side Auth" tab.
9+
// 2. enter the query into the form and change the list id in the path param
10+
// 3. set free variables to email, firstName, and lastName
11+
// 4. replace the doc_id below
12+
// 5. Set RAZZLE_ENABLE_MAILCHIMP_SIGNUP=true in .env
13+
const DOC_ID = 'd0b9c4f6-73b4-400c-b003-1692b67e12ca';
14+
/*
15+
# Query for d0b9c4f6-73b4-400c-b003-1692b67e12ca
16+
mutation MailchimpSignup($email: String!, $firstName: String!, $lastName: String!) {
17+
mailchimp {
18+
makeRestCall {
19+
post(
20+
path: "/3.0/lists/e6680e715f/members"
21+
jsonBody: {
22+
email_address: $email
23+
status: "pending"
24+
tags: ["changelog"]
25+
merge_fields: {
26+
FNAME: $firstName
27+
LNAME: $lastName
28+
}
29+
}
30+
) {
31+
jsonBody
32+
}
33+
}
34+
}
35+
}
36+
*/
37+
38+
async function subscribeToList({
39+
email,
40+
firstName,
41+
lastName,
42+
}: {
43+
email: string,
44+
firstName: string,
45+
lastName: string,
46+
}): Promise<any> {
47+
const resp = await fetch(
48+
'https://serve.onegraph.com/graphql?app_id=' +
49+
String(process.env.RAZZLE_ONEGRAPH_APP_ID),
50+
{
51+
method: 'POST',
52+
headers: {
53+
'Content-Type': 'application/json',
54+
Accept: 'application/json',
55+
},
56+
body: JSON.stringify({
57+
doc_id: DOC_ID,
58+
variables: {
59+
email,
60+
firstName,
61+
lastName,
62+
},
63+
}),
64+
},
65+
);
66+
const json = await resp.json();
67+
console.log('j', json);
68+
const body = idx(json, _ => _.data.mailchimp.makeRestCall.post.jsonBody);
69+
if (!body) {
70+
throw new Error('unknown error');
71+
}
72+
if (body.email_address) {
73+
return;
74+
}
75+
const errors = idx(
76+
json,
77+
_ => _.data.mailchimp.makeRestCall.post.jsonBody.errors,
78+
);
79+
80+
if (errors) {
81+
if (errors.find(e => e.field === 'email_address')) {
82+
throw new Error('invalid email');
83+
} else {
84+
throw new Error('unknown error');
85+
}
86+
}
87+
88+
const errorDetail = body.detail;
89+
if (errorDetail) {
90+
throw new Error(errorDetail);
91+
}
92+
}
93+
94+
export default function NewsletterSignup() {
95+
const [formFields, setFormFields] = React.useState({
96+
email: '',
97+
firstName: '',
98+
lastName: '',
99+
});
100+
const [isLoading, setIsLoading] = React.useState(false);
101+
const [error, setError] = React.useState(null);
102+
const [success, setSuccess] = React.useState(false);
103+
if (success) {
104+
return (
105+
<Box pad="medium" elevation="small">
106+
<Heading level={4} margin="none">
107+
Subscribed to updates!
108+
</Heading>
109+
<Text margin={{top: 'small'}} size="small">
110+
Check your email for the confirmation.
111+
</Text>
112+
</Box>
113+
);
114+
}
115+
return (
116+
<Box pad="medium" elevation="small">
117+
<Heading level={4} margin="none">
118+
Subscribe to updates
119+
</Heading>
120+
<Box margin={{top: 'small'}}>
121+
<Form
122+
errors={error ? {email: error} : {}}
123+
value={formFields}
124+
onSubmit={async e => {
125+
e.stopPropagation();
126+
e.preventDefault();
127+
setIsLoading(true);
128+
setError(null);
129+
try {
130+
await subscribeToList(formFields);
131+
setSuccess(true);
132+
} catch (e) {
133+
setError(
134+
`Error subscribing: ${e.message}
135+
Email [email protected] for help.`,
136+
);
137+
console.error('Error subscribing', e);
138+
} finally {
139+
setIsLoading(false);
140+
}
141+
}}>
142+
<FormField name="firstName˚">
143+
<TextInput
144+
disabled={isLoading}
145+
value={formFields.firstName}
146+
onChange={e =>
147+
setFormFields({
148+
...formFields,
149+
firstName: e.target.value,
150+
})
151+
}
152+
placeholder="First name"
153+
/>
154+
</FormField>
155+
<FormField name="lastName">
156+
<TextInput
157+
disabled={isLoading}
158+
value={formFields.lastName}
159+
onChange={e =>
160+
setFormFields({
161+
...formFields,
162+
lastName: e.target.value,
163+
})
164+
}
165+
placeholder="Last name"
166+
/>
167+
</FormField>
168+
<FormField name="email">
169+
<TextInput
170+
disabled={isLoading}
171+
value={formFields.email}
172+
onChange={e =>
173+
setFormFields({
174+
...formFields,
175+
email: e.target.value,
176+
})
177+
}
178+
placeholder="Email"
179+
/>
180+
</FormField>
181+
<Button disabled={isLoading} type="submit" label="Subscribe" />
182+
</Form>
183+
</Box>
184+
</Box>
185+
);
186+
}

src/Post.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,10 @@ export function PostBox({children}: {children: React.Node}) {
204204
return (
205205
<Box
206206
margin="medium"
207+
elevation="small"
207208
style={{
208209
maxWidth: 704,
209210
borderRadius: 2,
210-
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0,0,0,0.2)',
211211
}}>
212212
{children}
213213
</Box>

0 commit comments

Comments
 (0)