Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contact page fully functional. Utilizes Material UI design components and inherits styles from theme #1330

Merged
merged 38 commits into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
f3c8cc9
added sendContactData function and its entry into rootSaga in /client…
edwinjue Aug 23, 2022
2f6f6d2
refactored /client/components/main/ContactForm.jsx from class to func…
edwinjue Aug 23, 2022
025e3f7
added boilerplate scss files from v1 into client/styles for ContactFo…
edwinjue Aug 24, 2022
12782ae
moved all contact files under client/components/contact, added Button…
edwinjue Aug 25, 2022
43db4cf
initial refactor of contact form utilizing material-ui
edwinjue Aug 25, 2022
5821a6c
brought back the contact image
edwinjue Aug 25, 2022
7fb307d
applied form validation control to material-ui TextField components. …
edwinjue Aug 25, 2022
f3a73eb
added Grid component to ContactIntro. contact page is looking present…
edwinjue Aug 25, 2022
216db4b
contact form now shows a confirmation message upon successful submiss…
edwinjue Aug 26, 2022
7df6c06
reconfigured ToastContainer in Contact.jsx and toast calls in Contact…
edwinjue Aug 26, 2022
dbdb492
removed some blank lines to improve readability
edwinjue Aug 26, 2022
4c28f42
turned off autocomplete for all MUI TextField components in the Conta…
edwinjue Aug 26, 2022
dca1d27
display mui CircularProgress element when submit button clicked to di…
edwinjue Aug 26, 2022
b2c08e0
added a few comments and reorganized code in ContactForm.jsx to impro…
edwinjue Aug 26, 2022
2bb011f
added components/contact/styles/
edwinjue Aug 26, 2022
1ed2871
removed some unused files and comments
edwinjue Aug 26, 2022
d9c09e8
tiny formatting change
edwinjue Aug 26, 2022
799bf10
updated contactForm code per es-lint. added webpack.*.js to .eslintig…
edwinjue Aug 28, 2022
1c80214
replaced Grid justify property with justifyContent
edwinjue Aug 28, 2022
13f76a4
made some changes as per review comments
edwinjue Aug 30, 2022
0df22a7
made some more changes as per review comments
edwinjue Aug 30, 2022
07530cf
made some more changes as per review comments
edwinjue Aug 30, 2022
3414da3
removed a console.log in ContactForm
edwinjue Aug 30, 2022
437c9d2
refactored client/components/contact/ContactImage to make use of make…
edwinjue Aug 31, 2022
f6a54fe
removed explicit comparison to true in ternary operator used by Circu…
edwinjue Aug 31, 2022
95d2a0d
in conditional statements, removed use of type coercion on variables …
edwinjue Sep 1, 2022
8a03971
added early return in onSubmit handler to return false if validation …
edwinjue Sep 1, 2022
ad333f6
updated syntax of early return in onSubmit handler
edwinjue Sep 1, 2022
435d4a4
got rid of methods to dispatch redux actions defined on top and calle…
edwinjue Sep 1, 2022
60bcb80
removed explicit comparison to false in ternary operator used by Button
edwinjue Sep 1, 2022
b13a1d3
tested contact form submission offline. made necessary updates to ens…
edwinjue Sep 2, 2022
0e80c9f
removed comment // response: { status }
edwinjue Sep 3, 2022
ceb9a77
updated fix to metadata.js status is undefined console error
edwinjue Sep 3, 2022
4c2c5c2
removed contact/settings until contact form utilizes more configurati…
edwinjue Sep 3, 2022
aa82a82
reverted /client/redux/reducers/metadata.js and /client/redux/sagas/m…
edwinjue Sep 3, 2022
c1492a0
moved toast to bottom-right
edwinjue Sep 7, 2022
85be5b0
Merge branch 'dev' into 1176-auto-create-issues-of-contact-us-form-me…
edwinjue Sep 7, 2022
95327a6
eslint
edwinjue Sep 7, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/.eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
*.test.js*
v1/*
dist/*

webpack.*.js
4 changes: 2 additions & 2 deletions client/Routes.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Privacy from '@components/main/Privacy';
import Faqs from '@components/main/Faqs';
import About from '@components/main/About';
import Blog from '@components/main/Blog';
import ContactForm from '@components/main/ContactForm';
import Contact from '@components/contact/Contact';

export default function Routes() {
const { pathname } = useLocation();
Expand All @@ -28,7 +28,7 @@ export default function Routes() {
<Route path="/faqs" component={Faqs} />
<Route path="/about" component={About} />
<Route path="/blog" component={Blog} />
<Route path="/contact" component={ContactForm} />
<Route path="/contact" component={Contact} />
<Route path="/">
<Redirect to="map" />
</Route>
Expand Down
32 changes: 32 additions & 0 deletions client/components/contact/Contact.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import { ToastContainer } from 'react-toastify';
import ContactImage from './ContactImage';
import ContactIntro from './ContactIntro';
import ContactForm from './ContactForm';

import 'react-toastify/dist/ReactToastify.css';

const Contact = () => (
<>
<div>
<ToastContainer
position="top-right"
autoClose={5000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
/>
</div>
<div>
<ContactImage>Contact Us</ContactImage>
<ContactIntro />
<ContactForm />
</div>
</>
);

export default Contact;
248 changes: 248 additions & 0 deletions client/components/contact/ContactForm.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import React, { useState, useEffect, useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { sendGitRequest } from '@reducers/data';
import { showFeedbackSuccess, setErrorModal } from '@reducers/ui';
import { toast } from 'react-toastify';
import {
Container,
Grid,
Button,
TextField,
CircularProgress,
} from '@material-ui/core';
import contactSettings from './settings';
import 'react-toastify/dist/ReactToastify.css';

const initialFormValues = {
firstName: '',
lastName: '',
email: '',
association: '',
message: '',
errors: {
missingFirstName: false,
missingLastName: false,
missingEmail: false,
invalidEmail: false,
missingMessage: false,
},
loading: false,
};

const ContactForm = () => {
const dispatch = useDispatch();

// mapStateToProps equivalent.
const displayFeedbackSuccess = useSelector(state => state.ui.displayFeedbackSuccess);
const openErrorModal = useSelector(state => state.ui.error.isOpen);

const [formValues, setFormValues] = useState(initialFormValues);

function clearFields() {
setFormValues({
...initialFormValues,
});
}

// Initialize component.
useEffect(() => {
// componentDidMount code goes here...
clearFields();
if (displayFeedbackSuccess === true) {
toast.success('We received your message. Our team will contact you at the email address provided.', contactSettings.toast.dark);
}

if (openErrorModal === true) {
toast.error('We failed to process your message. Please try again later.', contactSettings.toast.dark);
}

return () => {
// componentWillUnmount code goes here...
dispatch(showFeedbackSuccess(false));
dispatch(setErrorModal(false));
clearFields();
};
}, [dispatch, displayFeedbackSuccess, openErrorModal]);

// Helper methods.
function validateEmail(emailAddress) {
// A regular expression checking for a valid email format.
const VALID_EMAIL_FORMAT_REGEX = /^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/;
return VALID_EMAIL_FORMAT_REGEX.test(emailAddress);
}

const clearErrors = useCallback(() => {
setFormValues(prevState => ({
...prevState,
...{
errors: {
missingFirstName: false,
missingLastName: false,
missingEmail: false,
invalidEmail: false,
missingMessage: false,
},
},
}));
}, []);

const validateForm = useCallback(() => {
const noFirstName = formValues.firstName.trim().length === 0;
const noLastName = formValues.lastName.trim().length === 0;
const noEmail = formValues.email.trim().length === 0;
const noMessage = formValues.message.trim().length === 0;
const incompleteEmail = (!noEmail && !validateEmail(formValues.email));
if (!noFirstName && !noLastName && !noEmail && !noMessage && !incompleteEmail) {
return true;
}

setFormValues(prevState => ({
...prevState,
...{
errors: {
missingFirstName: noFirstName,
missingLastName: noLastName,
missingEmail: noEmail,
invalidEmail: incompleteEmail,
missingMessage: noMessage,
},
},
}));
return false;
}, [formValues]);

// Event handlers.
const onInputChange = useCallback(event => {
const { name, value } = event.target;
setFormValues(prevState => ({ ...prevState, [name]: value }));
}, []);

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

if (!validateForm()) {
return;
}

const body = [
`First name: ${formValues.firstName.trim()}`,
`Last name: ${formValues.lastName.trim()}`,
`Email: ${formValues.email.trim()}`,
`Association: ${formValues.association.trim() || 'Not provided'}`,
`Message: ${formValues.message.trim()}`,
].join('\n');

setFormValues(prevState => ({
...prevState,
...{
loading: true,
},
}));

// Dispatch action to redux with payload.
dispatch(sendGitRequest({ title: formValues.email, body }));
}, [dispatch,
formValues.association,
formValues.email,
formValues.firstName,
formValues.lastName,
formValues.message,
validateForm]);

return (
<Container maxWidth="sm">
<form id="contact-form" onSubmit={handleSubmit}>
<Grid container alignItems="center" justifyContent="center" direction="column" style={{ gap: '10px' }}>
<Grid container alignItems="center" justifyContent="center" direction="row" spacing={2}>
<Grid item xs={6}>
<TextField
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like we have multiple TextFields with some subset of the same properties. Can we make a subclass or something of that sort so that we don't need to repeat all those properties?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to but I'm not sure if we can without creating our own TextField component that takes in an object that we can set as a prop but I think doing so would add more complexity to just using material-ui components as they are. However, if you or anyone else could suggest a way we could minimize repetitive code here, I'll be interested in trying it out

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jekijo or Jordan, any ideas?

id="contact-firstname"
name="firstName"
label="First Name *"
type="text"
autoComplete="off"
value={formValues.firstName}
onChange={onInputChange}
onFocus={clearErrors}
error={formValues.errors.missingFirstName}
helperText={formValues.errors.missingFirstName ? 'Please provide a first name.' : ''}
fullWidth
/>
</Grid>
<Grid item xs={6}>
<TextField
id="contact-lastname"
name="lastName"
label="Last Name *"
type="text"
autoComplete="off"
value={formValues.lastName}
onChange={onInputChange}
onFocus={clearErrors}
error={formValues.errors.missingLastName}
helperText={formValues.errors.missingLastName ? 'Please provide a last name.' : ''}
fullWidth
/>
</Grid>
</Grid>
<Grid container alignItems="center" justifyContent="center" direction="row">
<Grid item xs={12}>
<TextField
id="contact-email"
name="email"
label="Email *"
type="text"
autoComplete="off"
value={formValues.email}
onChange={onInputChange}
onFocus={clearErrors}
error={formValues.errors.missingEmail || formValues.errors.invalidEmail}
helperText={formValues.errors.missingEmail || formValues.errors.invalidEmail ? 'Please provide a valid email address.' : ''}
fullWidth
/>
</Grid>
<Grid item xs={12}>
<TextField
id="contact-association"
name="association"
label="Association"
type="text"
autoComplete="off"
value={formValues.association}
onChange={onInputChange}
fullWidth
/>
</Grid>
<Grid item xs={12} style={{ paddingTop: '8px' }}>
<TextField
id="contact-message"
name="message"
label="Message *"
type="text"
variant="outlined"
rows={4}
autoComplete="off"
value={formValues.message}
onChange={onInputChange}
onFocus={clearErrors}
error={formValues.errors.missingMessage}
helperText={formValues.errors.missingMessage ? 'Please provide a message.' : ''}
fullWidth
multiline
/>
</Grid>
</Grid>
<Grid container direction="column" alignItems="center" justifyContent="center" style={{ paddingTop: '8px' }}>
<CircularProgress style={{ display: formValues.loading ? 'block' : 'none' }} />
<Button variant="contained" color="primary" type="submit" style={{ display: formValues.loading ? 'none' : 'block' }}>
Submit
</Button>
</Grid>
</Grid>
</form>
</Container>

);
};

export default ContactForm;
43 changes: 43 additions & 0 deletions client/components/contact/ContactImage.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import PropTypes from 'prop-types';
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import coverImage from '@assets/contact_bg.png';

const useStyles = makeStyles(() => ({
contactImageCover: {
height: '25vh',
backgroundImage: `url(${coverImage})`,
backgroundPosition: 'top',
backgroundRepeat: 'no-repeat',
backgroundSize: 'cover',
position: 'relative',

},
contactImageOverlayText: {
left: '50%',
fontSize: '40px',
fontWeight: 'bold',
position: 'absolute',
textAlign: 'center',
top: '50%',
transform: 'translate(-50%, -50%)',
},
}));

const ContactImage = ({ children }) => {
const classes = useStyles();

return (
<div className={classes.contactImageCover}>
<div className={classes.contactImageOverlayText}>
<span>{children}</span>
</div>
</div>
);
};

ContactImage.propTypes = {
children: PropTypes.node.isRequired,
};

export default ContactImage;
22 changes: 22 additions & 0 deletions client/components/contact/ContactIntro.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import React from 'react';
import {
Grid,
} from '@material-ui/core';

const ContactIntro = () => (
<Grid container alignItems="center" justify="center" direction="column">
<Grid item style={{ width: '45%' }}>
<h1 style={{ textAlign: 'center' }}>
{'Don\'t See What You Need?'}
</h1>
<p>
We want to build a tool that works for you. We are open to suggestions and
feedback and would love the opportunity to get connected. Feel free to input
your information in the contact form below and we will be sure to get back to
you within 2-3 business days. Thank you!
</p>
</Grid>
</Grid>
);

export default ContactIntro;
16 changes: 16 additions & 0 deletions client/components/contact/settings/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Configuration settings used by the contact page
const contactSettings = {
edwinjue marked this conversation as resolved.
Show resolved Hide resolved
toast: {
dark: {
position: 'top-right',
autoClose: 5000,
hideProgressBar: false,
closeOnClick: true,
pauseOnHover: true,
draggable: true,
progress: undefined,
},
},
};

export default contactSettings;
Loading