Skip to content

Commit 6e7365b

Browse files
authored
Merge pull request #136 from Web-Dev-Path/feature/contact-us-form-visual-2
Implement Contact Us Form Funtionalities
2 parents 9eb56b1 + a2eeebe commit 6e7365b

16 files changed

+612
-19
lines changed

.env-template

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ To get rid of the `console.log` errors related to MailChimp and reCapctha:
66
NEXT_PUBLIC_MAILCHIMP_URL={Add the url link}
77
NEXT_PUBLIC_RECAPTCHA_SITE_KEY={Add the key}
88
RECAPTCHA_SECRET_KEY={Add the key}
9+
SENDGRID_API_KEY={Add the key}

CHANGELOG.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
4646

4747
- Made the site a Progressive Web App (PWA)
4848
- About page content (first section)
49+
- Contact us form and decoration components
4950
- About page "Wanna Learn More" and "How to get started?" section
5051
- An optional second column to TwoColumn instead of the image
5152
- .prettierignore file
5253
- husky, lint-staged to auto format with prettier on git commit
5354
- lint and format script to run prettier check and write on all files respectively
55+
- contact form functionalities (email form using sendgrid, subscription if selected, google recaptcha)
5456
- who we are section to about page
5557
- still got questions section to about page
5658

59+
60+
5761
### Fixed
5862

5963
- component file structure
@@ -67,7 +71,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
6771
- updated 'about us' section (our goals, our purpose)
6872
- updated mobile nav to automatically close when page route change is completed
6973
- adjust flex-basis of a few sections in the about page to better match the design file
74+
75+
76+
77+
### Updated
78+
7079
- prettierrc "end of line" to auto
80+
- .env-template to include SENDGRID_API_KEY
7181
- fixed next.js warning - no stylesheets in head component
7282
(added _document.js and moved google fonts into _document.js)
7383

84+

components/ContactUsForm.js

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import { useForm } from 'react-hook-form';
2+
import { useRef } from 'react';
3+
import MailchimpSubscribe from 'react-mailchimp-subscribe';
4+
import ReCAPTCHA from 'react-google-recaptcha';
5+
import Container from '@/components/containers/Container';
6+
import contactUsFormStyles from '@/styles/Contact.module.scss';
7+
import RevealContentContainer from '@/components/containers/RevealContentContainer';
8+
import SubmitButton from '@/components/buttons/SubmitButton';
9+
10+
export const ContactUsFormSubscribe = ({ setMsg }) => {
11+
return (
12+
<MailchimpSubscribe
13+
url={process.env.NEXT_PUBLIC_MAILCHIMP_URL}
14+
render={({ subscribe, status, message }) => {
15+
console.info(`MailChimp (contact form): ${status} - ${message}`);
16+
return (
17+
<>
18+
<ContactUsForm
19+
subscribe={formData => subscribe(formData)}
20+
setResponseMessage={setMsg}
21+
/>
22+
{status === 'error' && (
23+
<div
24+
className={contactUsFormStyles.contact__respseonErrorMessage}
25+
>
26+
{`Newsletter subscription error: ${message}`}
27+
</div>
28+
)}
29+
</>
30+
);
31+
}}
32+
/>
33+
);
34+
};
35+
36+
function ContactUsForm({ subscribe, setResponseMessage }) {
37+
const contactReCaptchaRef = useRef();
38+
39+
const {
40+
register,
41+
handleSubmit,
42+
reset,
43+
formState: { errors, isSubmitting },
44+
} = useForm({
45+
defaultValues: {
46+
Name: '',
47+
Email: '',
48+
Subject: '',
49+
Message: '',
50+
},
51+
});
52+
53+
async function onSubmit(data) {
54+
setResponseMessage(['Submitting...']);
55+
56+
contactReCaptchaRef.current.reset();
57+
const gReCaptchaToken = await contactReCaptchaRef.current.executeAsync();
58+
59+
const res = await fetch('/api/contact', {
60+
method: 'POST',
61+
headers: {
62+
'Content-Type': 'application/json',
63+
},
64+
body: JSON.stringify({
65+
name: data.Name,
66+
email: data.Email,
67+
subject: data.Subject,
68+
message: data.Message,
69+
subscribe: data.Subscribe,
70+
gReCaptchaToken,
71+
}),
72+
});
73+
74+
if (res.ok) {
75+
setResponseMessage([
76+
'Your message was sent successfully. We will be in touch with you as soon as possible.',
77+
]);
78+
} else {
79+
const jsonRes = await res.json();
80+
setResponseMessage([
81+
'Error Submitting Message',
82+
`Status Code: ${res.status} - ${jsonRes.message}`,
83+
'Please contact support at [email protected]',
84+
]);
85+
}
86+
87+
if (data.Subscribe) {
88+
subscribe({ EMAIL: data.Email });
89+
}
90+
reset();
91+
}
92+
93+
return (
94+
<RevealContentContainer>
95+
<Container>
96+
<form
97+
onSubmit={handleSubmit(onSubmit)}
98+
className={contactUsFormStyles.contact__form}
99+
>
100+
<input
101+
type='text'
102+
placeholder='name'
103+
{...register('Name', {
104+
required: true,
105+
minLength: 2,
106+
maxLength: 80,
107+
//no white space pattern
108+
pattern: /[^\s-]/i,
109+
})}
110+
className={`${contactUsFormStyles.contact__input} ${contactUsFormStyles.contact__name}`}
111+
/>
112+
<p className={contactUsFormStyles.contact__errorMessage}>
113+
{errors.Name?.type === 'required'
114+
? 'Name is required'
115+
: errors.Name?.type === 'pattern'
116+
? 'No whitespace'
117+
: errors.Name?.type === 'minLength'
118+
? 'Must be more than 1 character'
119+
: undefined}
120+
</p>
121+
<input
122+
type='email'
123+
placeholder='email'
124+
{...register('Email', {
125+
required: true,
126+
pattern: /^\S+@\S+$/i,
127+
})}
128+
className={`${contactUsFormStyles.contact__input} ${contactUsFormStyles.contact__email}`}
129+
/>
130+
<p className={contactUsFormStyles.contact__errorMessage}>
131+
{errors.Email?.type === 'required' && 'Email is required'}
132+
</p>
133+
<input
134+
type='text'
135+
placeholder='subject'
136+
{...register('Subject', {
137+
required: true,
138+
minLength: 2,
139+
pattern: /[^\s-]/i,
140+
})}
141+
className={`${contactUsFormStyles.contact__input} ${contactUsFormStyles.contact__subject}`}
142+
/>
143+
<p className={contactUsFormStyles.contact__errorMessage}>
144+
{errors.Subject?.type === 'required'
145+
? 'Subject is required'
146+
: errors.Subject?.type === 'pattern'
147+
? 'No whitespace'
148+
: errors.Subject?.type === 'minLength'
149+
? 'Must be more than 1 character'
150+
: undefined}
151+
</p>
152+
<textarea
153+
{...register('Message', {
154+
required: true,
155+
minLength: 2,
156+
pattern: /[^\s-]/i,
157+
})}
158+
placeholder='Write your message here'
159+
className={`${contactUsFormStyles.contact__input} ${contactUsFormStyles.contact__message}`}
160+
/>
161+
<p className={contactUsFormStyles.contact__errorMessage}>
162+
{errors.Message?.type === 'required'
163+
? 'Message is required'
164+
: errors.Message?.type === 'pattern'
165+
? 'No whitespace'
166+
: errors.Message?.type === 'minLength'
167+
? 'Must be more than 1 character'
168+
: undefined}
169+
</p>
170+
<div className={contactUsFormStyles.contact__subscribe}>
171+
<input
172+
className={contactUsFormStyles.contact__subscribeInput}
173+
type='checkbox'
174+
placeholder='Subscribe to our DevNews!'
175+
{...register('Subscribe', {})}
176+
/>
177+
Subscribe to our DevNews!
178+
</div>
179+
<SubmitButton label='Submit' disabled={isSubmitting} />
180+
181+
<ReCAPTCHA
182+
ref={contactReCaptchaRef}
183+
size='invisible'
184+
sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY}
185+
/>
186+
</form>
187+
</Container>
188+
</RevealContentContainer>
189+
);
190+
}

components/buttons/SubmitButton.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import btnStyles from '@/styles/SubmitButton.module.scss';
2+
3+
export default function SubmitButton({ customClassName, label, disabled }) {
4+
return (
5+
<button
6+
className={
7+
customClassName
8+
? `${btnStyles.btn} ${btnStyles[customClassName]}`
9+
: btnStyles.btn
10+
}
11+
type='submit'
12+
disabled={disabled}
13+
>
14+
{label}
15+
</button>
16+
);
17+
}

components/mailchimp/NewsletterForm.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { createRef, useState } from 'react';
22
import Image from 'next/image';
33
import { decode } from 'html-entities';
4+
import ReCAPTCHA from 'react-google-recaptcha';
45
import Container from '@/components/containers/Container';
56
import newsletterStyles from '@/styles/Newsletter.module.scss';
6-
import ReCAPTCHA from 'react-google-recaptcha';
7+
import SubmitButton from '@/components/buttons/SubmitButton';
78

89
const SITE_KEY = process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY;
910

@@ -149,12 +150,10 @@ const NewsletterForm = ({ status, message, onValidated }) => {
149150
placeholder='email'
150151
onKeyUp={event => handleInputKeyEvent(event)}
151152
/>
152-
<button
153-
className={newsletterStyles.newsletter__button}
154-
type='submit'
155-
>
156-
Subscribe
157-
</button>
153+
<SubmitButton
154+
label='Subscribe'
155+
customClassName='newsletter__button'
156+
/>
158157

159158
<ReCAPTCHA
160159
ref={recaptchaRef}

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,14 @@
1919
]
2020
},
2121
"dependencies": {
22+
"@sendgrid/mail": "^7.7.0",
2223
"html-entities": "^2.3.2",
2324
"next": "^12.2.2",
2425
"next-pwa": "^5.5.4",
2526
"react": "^18.2.0",
2627
"react-dom": "^18.2.0",
2728
"react-google-recaptcha": "^2.1.0",
29+
"react-hook-form": "^7.35.0",
2830
"react-mailchimp-subscribe": "^2.1.3",
2931
"sass": "^1.35.1",
3032
"swiper": "^8.2.2"

pages/api/contact.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Sends email to hello@webdevpath.co when user submit the form in "Contact Us" page
2+
import sendgrid from '@sendgrid/mail';
3+
4+
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
5+
6+
export default async (req, res) => {
7+
if (req.method !== 'POST') {
8+
res.status(405).send({ message: 'Only POST requests allowed' });
9+
return;
10+
}
11+
12+
try {
13+
const {
14+
name,
15+
email: emailAddress,
16+
subject,
17+
message,
18+
subscribe,
19+
gReCaptchaToken,
20+
} = req.body;
21+
22+
// verify recaptcha token
23+
const reCaptchaValidation = await (
24+
await fetch('https://www.google.com/recaptcha/api/siteverify', {
25+
method: 'POST',
26+
headers: {
27+
'Content-Type': 'application/x-www-form-urlencoded',
28+
},
29+
body: `secret=${process.env.RECAPTCHA_SECRET_KEY}&response=${req.body.gReCaptchaToken}`,
30+
})
31+
).json();
32+
33+
if (reCaptchaValidation.success) {
34+
// TODO: change the emails to '[email protected]' before PR
35+
// receiverEmail: The email will be sent here
36+
const receiverEmail = '[email protected]';
37+
// sendgridEmail: This is the email verfied by sendgrid
38+
// the email will appear to be sent from this email
39+
// If a non verified email is used, we get a 403 error
40+
const sendgridEmail = '[email protected]';
41+
42+
const emailContent = `
43+
<b>Name:</b> ${name} <br/>
44+
<b>Email:</b> <a href='mailto:${emailAddress}'>${emailAddress}</a><br/><br/>
45+
<u><b>Subject:</b> ${subject}</u><br/>
46+
<b>Message:</b> ${message} <br/>
47+
<b>Subscribe?:</b> ${subscribe ? 'Yes' : 'No'}
48+
`;
49+
50+
const email = {
51+
to: receiverEmail,
52+
from: {
53+
email: sendgridEmail,
54+
name: 'Web Dev Path',
55+
},
56+
replyTo: {
57+
email: emailAddress,
58+
name,
59+
},
60+
subject: `New message from ${name} via webdevpath.co 'Contact Us' Form`,
61+
content: [
62+
{
63+
type: 'text/html',
64+
value: emailContent,
65+
},
66+
],
67+
};
68+
await sendgrid.send(email);
69+
res.status(200).json({ status: 'OK' });
70+
} else {
71+
return res.status(422).json({
72+
status: 'error',
73+
message: 'Invalid ReCaptcha',
74+
});
75+
}
76+
} catch (e) {
77+
return res.status(500).json({
78+
status: 'error',
79+
message: `Error: ${e.message}`,
80+
});
81+
}
82+
};

0 commit comments

Comments
 (0)