Skip to content

Commit

Permalink
Add forms
Browse files Browse the repository at this point in the history
  • Loading branch information
travismiller committed Mar 27, 2021
1 parent 5efe44e commit fce1f23
Show file tree
Hide file tree
Showing 14 changed files with 7,849 additions and 159 deletions.
26 changes: 26 additions & 0 deletions functions/email-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const { getAuthToken } = require('../src/lib/google-sheets/service')
const { credentials, emailList: {spreadsheetId, spreadsheetTab} } = require('../src/lib/google-sheets/config')
const { emailListSubmit } = require('../src/lib/google-sheets/data')

async function main({email, name}) {
const auth = await getAuthToken({credentials})
return await emailListSubmit({auth, spreadsheetId, spreadsheetTab, email, name})
}

function error(err, statusCode = 500) {
return {
body: err.toString(),
headers: { 'Content-Type': 'application/json' },
statusCode
}
}

function ok(data, statusCode = 200) {
return {
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
statusCode
}
}

exports.handler = async (event, _context, _callback) => main(JSON.parse(event.body)).then(ok).catch(error)
26 changes: 26 additions & 0 deletions functions/message.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
const { getAuthToken } = require('../src/lib/google-sheets/service')
const { credentials, messages: {spreadsheetId, spreadsheetTab} } = require('../src/lib/google-sheets/config')
const { messageSubmit } = require('../src/lib/google-sheets/data')

async function main({name, email, message}) {
const auth = await getAuthToken({credentials})
return await messageSubmit({auth, spreadsheetId, spreadsheetTab, name, email, message})
}

function error(err, statusCode = 500) {
return {
body: err.toString(),
headers: { 'Content-Type': 'application/json' },
statusCode
}
}

function ok(data, statusCode = 200) {
return {
body: JSON.stringify(data),
headers: { 'Content-Type': 'application/json' },
statusCode
}
}

exports.handler = async (event, _context, _callback) => main(JSON.parse(event.body)).then(ok).catch(error)
1 change: 1 addition & 0 deletions netlify.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[build]
command = "make build"
publish = "out"
functions = "functions/"

[[plugins]]
package = "@netlify/plugin-nextjs"
11 changes: 10 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,31 @@
"scripts": {
"dev": "next dev",
"build": "next build && next export",
"start": "next start"
"start": "next start",
"netlify:dev": "netlify dev -c 'next dev'"
},
"dependencies": {
"@netlify/plugin-nextjs": "^3.0.3",
"autoprefixer": "^10.2.4",
"axios": "^0.21.1",
"classnames": "^2.2.6",
"google-auth-library": "^7.0.3",
"googleapis": "^68.0.0",
"gray-matter": "^4.0.2",
"luxon": "^1.26.0",
"next": "10.0.6",
"next-compose-plugins": "^2.2.1",
"next-images": "^1.7.0",
"next-plugin-yaml": "^1.0.1",
"postcss": "^8.2.4",
"react": "17.0.1",
"react-dom": "17.0.1",
"react-query": "^3.13.0",
"remark": "^13.0.0",
"remark-html": "^13.0.1",
"tailwindcss": "^2.0.2"
},
"devDependencies": {
"netlify-cli": "^3.13.7"
}
}
75 changes: 75 additions & 0 deletions src/components/email-list-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React from 'react'
import axios from 'axios'
import styles from '@/styles/footer-email-list-form.module.css'

const formInit = () => {
return {
name: '',
email: '',
className: styles.form,
status: 'enabled'
}
}

const formReducer = (state, action) => {
switch (action.type) {
case 'change':
return {...state, ...action.payload}
case 'disabled':
return {...state, status: 'disabled', className: styles.formDisabled}
case 'success':
return {...state, status: 'success', className: styles.formSuccess}
case 'reset':
default:
return formInit()
}
}

export default function EmailListForm() {
const [form, dispatchForm] = React.useReducer(formReducer, formInit())

const handleChange = (field) => {
return (event) => dispatchForm({type: 'change', payload: {[field]: event.target.value}})
}

const handleSubmit = (event) => {
event.preventDefault()

dispatchForm({type: 'disabled'})

const {name, email} = form

axios
.post('/.netlify/functions/email-list', {name, email})
.then(_response => dispatchForm({type: 'success'}))
}

return (
<div className="; relative">
<form onSubmit={handleSubmit} className={form.className}>
<label>
<div className="; hidden">Name</div>
<div><input required type="text" placeholder="Name" value={form.name} onChange={handleChange('name')} disabled={form.disabled} /></div>
</label>
<label>
<div className="; hidden">Email Address</div>
<div><input required type="email" placeholder="Email Address" value={form.email} onChange={handleChange('email')} disabled={form.disabled} /></div>
</label>
<div>
<button type="submit" disabled={form.disabled} className={styles.submitButton}>Subscribe Now</button>
</div>
</form>

{form.status == 'success' && (
<div className={styles.successOverlay}>
<div className="; flex flex-col text-center justify-evenly w-full h-full text-lg">
<div>Thanks for Subscribing!</div>
<div>
<button className={styles.successOkButton} onClick={() => dispatchForm({type: 'reset'})}>OK</button>
</div>
</div>
</div>
)}
</div>
)
}
3 changes: 3 additions & 0 deletions src/components/footer.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import EmailListForm from '@/components/email-list-form'

export default function Footer() {
return (
<footer className="; bg-wixColor15 text-wixColor13" style={{
Expand Down Expand Up @@ -49,6 +51,7 @@ export default function Footer() {
}}>
Join Our Email List
</h6>
<EmailListForm />
</div>
</div>
</footer>
Expand Down
80 changes: 80 additions & 0 deletions src/components/message-form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from 'react'
import axios from 'axios'
import styles from '@/styles/message-form.module.css'

const formInit = () => {
return {
name: '',
email: '',
message: '',
className: styles.form,
status: 'enabled'
}
}

const formReducer = (state, action) => {
switch (action.type) {
case 'change':
return {...state, ...action.payload}
case 'disabled':
return {...state, status: 'disabled', className: styles.formDisabled}
case 'success':
return {...state, status: 'success', className: styles.formSuccess}
case 'reset':
default:
return formInit()
}
}

export default function MessageForm() {
const [form, dispatchForm] = React.useReducer(formReducer, formInit())

const handleChange = (field) => {
return (event) => dispatchForm({type: 'change', payload: {[field]: event.target.value}})
}

const handleSubmit = (event) => {
event.preventDefault()

dispatchForm({type: 'disabled'})

const {name, email, message} = form

axios
.post('/.netlify/functions/message', {name, email, message})
.then(_response => dispatchForm({type: 'success'}))
}

return (
<div className="; relative p-4">
<form onSubmit={handleSubmit} className={form.className}>
<label>
<div className="; hidden">Name</div>
<div><input required type="text" placeholder="Name" value={form.name} onChange={handleChange('name')} disabled={form.disabled} /></div>
</label>
<label>
<div className="; hidden">Email Address</div>
<div><input required type="email" placeholder="Email Address" value={form.email} onChange={handleChange('email')} disabled={form.disabled} /></div>
</label>
<label>
<div className="; hidden">Message</div>
<div><textarea required type="email" placeholder="Message" rows="5" value={form.message} onChange={handleChange('message')} disabled={form.disabled} /></div>
</label>
<div>
<button type="submit" disabled={form.disabled} className={styles.submitButton}>Submit</button>
</div>
</form>

{form.status == 'success' && (
<div className={styles.successOverlay}>
<div className="; flex flex-col text-center justify-evenly w-full h-full text-lg">
<div>Thanks for submitting!</div>
<div>
<button className={styles.successOkButton} onClick={() => dispatchForm({type: 'reset'})}>OK</button>
</div>
</div>
</div>
)}
</div>
)
}
11 changes: 11 additions & 0 deletions src/lib/google-sheets/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
credentials: JSON.parse(Buffer.from(process.env.GOOGLE_APPLICATION_CREDENTIAL_BODY, 'base64')),
emailList: {
spreadsheetId: process.env.GOOGLE_SHEET_ID_EMAIL_LIST,
spreadsheetTab: 'Raw Input',
},
messages: {
spreadsheetId: process.env.GOOGLE_SHEET_ID_MESSAGES,
spreadsheetTab: 'Raw Input',
},
}
34 changes: 34 additions & 0 deletions src/lib/google-sheets/data.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const { DateTime } = require('luxon')
const { appendSpreadSheetValues } = require('./service.js')

const zone = 'America/Chicago'
const format = 'M/d/yyyy h:mm:ss'

const submitted = () => DateTime.local().setZone(zone).toFormat(format)

async function emailListSubmit({auth, spreadsheetId, spreadsheetTab, name, email}) {
const sheetName = spreadsheetTab
const values = [
[submitted(), name, email]
]

const response = await appendSpreadSheetValues({spreadsheetId, sheetName, auth, values})

return response
}

async function messageSubmit({auth, spreadsheetId, spreadsheetTab, name, email, message}) {
const sheetName = spreadsheetTab
const values = [
[submitted(), name, email, message]
]

const response = await appendSpreadSheetValues({spreadsheetId, sheetName, auth, values})

return response
}

module.exports = {
emailListSubmit,
messageSubmit,
}
52 changes: 52 additions & 0 deletions src/lib/google-sheets/service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const { google } = require('googleapis')
const { GoogleAuth } = require('google-auth-library')
const sheets = google.sheets('v4')
const SCOPES = [
'https://www.googleapis.com/auth/drive',
'https://www.googleapis.com/auth/spreadsheets'
]

async function getAuthToken({credentials}) {
const auth = new GoogleAuth({
credentials,
scopes: SCOPES
})
return auth.getClient()
}

async function getSpreadSheet({spreadsheetId, auth}) {
return sheets.spreadsheets.get({
spreadsheetId,
auth,
})
}

async function getSpreadSheetValues({spreadsheetId, auth, sheetName}) {
return sheets.spreadsheets.values.get({
spreadsheetId,
auth,
range: sheetName
})
}

async function appendSpreadSheetValues({spreadsheetId, auth, sheetName, values}) {
const valueInputOption = 'USER_ENTERED'
const resource = {
majorDimension: 'ROWS',
values,
}
return sheets.spreadsheets.values.append({
spreadsheetId,
auth,
range: sheetName,
valueInputOption,
resource,
})
}

module.exports = {
getAuthToken,
getSpreadSheet,
getSpreadSheetValues,
appendSpreadSheetValues,
}
12 changes: 10 additions & 2 deletions src/pages/contact.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from 'react'
import Title from '@/components/title'
import Banner from '@/components/banner'
import MailboxBanner2 from '@/images/wix/banners/MailboxBanner2.jpg'
import MessageForm from '@/components/message-form'

import ComputerTable from '@/images/wix/contact/computer-table.jpg'
import MailTruck from '@/images/wix/contact/mail-truck.jpg'
Expand Down Expand Up @@ -73,10 +74,17 @@ export default function Contact() {

<div className="; text-center p-4 flex flex-col space-y-4 sm:flex-row sm:space-x-4 sm:space-y-0">
<Tile src={ComputerTable}>
Send a Message
<h5 className="; uppercase m-4" style={{
fontSize: '30px',
textShadow: 'rgba(0, 0, 0, 0.4) 0px 4px 5px',
}}>Send a Message</h5>
<MessageForm />
</Tile>
<Tile src={MailTruck}>
Mail a Letter
<h5 className="; uppercase m-4" style={{
fontSize: '30px',
textShadow: 'rgba(0, 0, 0, 0.4) 0px 4px 5px',
}}>Mail a Letter</h5>

<div className="; text-2xl sm:text-4xl bg-white bg-opacity-75 border-wixColor15 border rounded-md m-8 py-8 px-4" style={{
boxShadow: '-5.00px 8.66px 4px 0px rgba(0, 0, 0, 0.6)',
Expand Down
Loading

0 comments on commit fce1f23

Please sign in to comment.