Skip to content

Gig work email landing #5091

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

Merged
merged 6 commits into from
Oct 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ workflows:
context : org-global
filters:
branches:
only:
- gig-details-fixes
only:
- hot-fix
# This is alternate dev env for parallel testing
- "build-qa":
context : org-global
Expand Down
5 changes: 5 additions & 0 deletions src/shared/components/Contentful/AppComponent/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import React from 'react';
import { errors } from 'topcoder-react-lib';
import Leaderboard from 'containers/tco/Leaderboard';
import RecruitCRMJobs from 'containers/Gigs/RecruitCRMJobs';
import EmailSubscribeForm from 'containers/EmailSubscribeForm';


const { fireErrorMessage } = errors;

Expand All @@ -34,6 +36,9 @@ export function AppComponentSwitch(appComponent) {
if (appComponent.fields.type === 'RecruitCRM-Jobs') {
return <RecruitCRMJobs {...appComponent.fields.props} key={appComponent.sys.id} />;
}
if (appComponent.fields.type === 'EmailSubscribeForm') {
return <EmailSubscribeForm {...appComponent.fields.props} key={appComponent.sys.id} />;
}
fireErrorMessage('Unsupported app component type from contentful', '');
return null;
}
Expand Down
221 changes: 221 additions & 0 deletions src/shared/containers/EmailSubscribeForm/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/**
* Genetic subscribe for MailChimp tags component
*/
import React from 'react';
import PT from 'prop-types';
import { isValidEmail } from 'utils/tc';
import TextInput from 'components/GUIKit/TextInput';
import _ from 'lodash';
import LoadingIndicator from 'components/LoadingIndicator';
import { Link } from 'topcoder-react-utils';
import defaulTheme from './style.scss';

/* Holds the base URL of Community App endpoints that proxy HTTP request to
* mailchimp APIs. */
const PROXY_ENDPOINT = '/api/mailchimp';

class SubscribeMailChimpTagContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
formErrors: {},
formData: {},
};
this.onSubscribeClick = this.onSubscribeClick.bind(this);
this.onFormInputChange = this.onFormInputChange.bind(this);
this.validateForm = this.validateForm.bind(this);
}

onSubscribeClick() {
this.validateForm();
// eslint-disable-next-line consistent-return
this.setState((state) => {
const { formData, formErrors } = state;
if (_.isEmpty(formErrors)) {
const { listId, tags } = this.props;
const fetchUrl = `${PROXY_ENDPOINT}/${listId}/members/${formData.email}/tags`;
const data = {
email_address: formData.email,
status: 'subscribed',
tags: tags.map(t => ({ name: t, status: 'active' })),
merge_fields: {
FNAME: formData.fname,
LNAME: formData.lname,
},
};
fetch(fetchUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
}).then(result => result.json()).then((dataResponse) => {
if (dataResponse.status === 204) {
// regist success
return this.setState({
subscribing: false,
subsribed: true,
error: '',
});
}
if (dataResponse.status === 404) {
// new email register it for list and add tags
data.tags = tags;
return fetch(`${PROXY_ENDPOINT}/${listId}/members`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(result => result.json()).then((rsp) => {
this.setState({
subscribing: false,
subsribed: !rsp.detail,
error: rsp.detail ? rsp.title : '',
});
});
}
return this.setState({
subscribing: false,
subsribed: false,
error: `Error ${dataResponse.status} when assigning tags to ${formData.email}`,
});
})
.catch((e) => {
this.setState({
subscribing: false,
subsribed: false,
error: e.message,
});
});
return { subscribing: true };
}
});
}

onFormInputChange(key, val) {
this.setState((state) => {
const { formData } = state;
formData[key] = val;
return {
...state,
formData,
};
});
this.validateForm(key);
}

validateForm(key) {
this.setState((state) => {
const { formData, formErrors } = state;
if (key) {
// validate only the key
if (!formData[key] || !_.trim(formData[key])) formErrors[key] = 'Required field';
else if (key === 'email' && !(isValidEmail(formData.email))) formErrors.email = 'Invalid email';
else delete formErrors[key];
} else {
_.each(['fname', 'lname', 'email'], (rkey) => {
if (!formData[rkey] || !_.trim(formData[rkey])) formErrors[rkey] = 'Required field';
else if (rkey === 'email' && !(isValidEmail(formData.email))) formErrors.email = 'Invalid email';
else delete formErrors[key];
});
}
// updated state
return {
...state,
formErrors,
};
});
}

render() {
const {
formData, formErrors, subscribing, subsribed, error,
} = this.state;
const {
btnText, title, successTitle, successText, successLink, successLinkText,
} = this.props;
return (
<div className={defaulTheme.wrapper}>
{
subscribing ? (
<div className={defaulTheme.loadingWrap}>
<LoadingIndicator />
<p className={defaulTheme.loadingText}>
Processing your subscription...
</p>
</div>
) : null
}
{
subsribed || error ? (
<div className={defaulTheme.subscribedWrap}>
<h4>{error ? 'OOPS!' : successTitle}</h4>
<p className={error ? defaulTheme.errorMsg : null}>{error || successText}</p>
{
error
? <button type="button" onClick={() => { window.location.reload(); }} className={defaulTheme.button}>TRY AGAIN</button>
: <Link to={successLink} className={defaulTheme.button}>{successLinkText}</Link>
}
</div>
) : null
}
{
!subscribing && !subsribed && !error ? (
<React.Fragment>
<h6>{title}</h6>
<TextInput
placeholder="First Name"
label="First Name"
onChange={val => this.onFormInputChange('fname', val)}
errorMsg={formErrors.fname}
value={formData.fname}
required
/>
<TextInput
placeholder="Last Name"
label="Last Name"
onChange={val => this.onFormInputChange('lname', val)}
errorMsg={formErrors.lname}
value={formData.lname}
required
/>
<TextInput
placeholder="Email Address"
label="Email Address"
onChange={val => this.onFormInputChange('email', val)}
errorMsg={formErrors.email}
value={formData.email}
required
/>
<button type="button" onClick={this.onSubscribeClick} disabled={!_.isEmpty(formErrors) || subscribing} className={defaulTheme.button}>{btnText}</button>
</React.Fragment>
) : null
}
</div>
);
}
}

SubscribeMailChimpTagContainer.defaultProps = {
title: '',
btnText: '',
successTitle: 'Success!',
successText: '',
successLink: '',
successLinkText: '',
};

SubscribeMailChimpTagContainer.propTypes = {
listId: PT.string.isRequired,
tags: PT.arrayOf(PT.string).isRequired,
title: PT.string,
btnText: PT.string,
successTitle: PT.string,
successText: PT.string,
successLink: PT.string,
successLinkText: PT.string,
};

export default SubscribeMailChimpTagContainer;
98 changes: 98 additions & 0 deletions src/shared/containers/EmailSubscribeForm/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
@import "~components/Contentful/default";

.loadingWrap {
margin: 0;
padding: 0 34px;

@include gui-kit-headers;
@include gui-kit-content;
@include roboto-regular;

.loadingText {
font-family: Roboto, sans-serif;
color: #2a2a2a;
text-align: center;
margin-top: 26px;
}
}

.subscribedWrap {
display: flex;
flex-direction: column;
align-items: center;
padding: 0 34px;

@include gui-kit-headers;
@include gui-kit-content;
@include roboto-regular;

> h4 {
margin-bottom: 16px !important;
}

> p {
font-size: 24px !important;
line-height: 36px !important;
text-align: center;

&.errorMsg {
color: #ef476f;
}
}
}

.wrapper {
display: flex;
flex-direction: column;
color: #2a2a2a;

@include gui-kit-headers;
@include gui-kit-content;
@include roboto-regular;

h6 {
margin-top: 41px;

@include xs-to-sm {
margin-top: 0;
}
}

> div {
margin-bottom: 8px;
}

.button {
background-color: #137d60;
border-radius: 20px;
color: #fff !important;
font-size: 14px;
font-weight: bolder;
text-decoration: none;
text-transform: uppercase;
line-height: 40px;
padding: 0 20px;
border: none;
outline: none;
margin-top: 13px;
margin-bottom: 41px;
max-width: 150px;

&:hover {
box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2);
background-color: #0ab88a;
}

@include xs-to-sm {
margin-bottom: 20px;
}

&:disabled {
background-color: #e9e9e9 !important;
border: none !important;
text-decoration: none !important;
color: #fafafb !important;
box-shadow: none !important;
}
}
}