Skip to content

Commit 4279b8d

Browse files
Merge pull request #5091 from topcoder-platform/gig-work-email-landing
Gig work email landing
2 parents 3627785 + 2688932 commit 4279b8d

File tree

4 files changed

+326
-2
lines changed

4 files changed

+326
-2
lines changed

.circleci/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,8 +236,8 @@ workflows:
236236
context : org-global
237237
filters:
238238
branches:
239-
only:
240-
- gig-details-fixes
239+
only:
240+
- hot-fix
241241
# This is alternate dev env for parallel testing
242242
- "build-qa":
243243
context : org-global

src/shared/components/Contentful/AppComponent/index.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import React from 'react';
1010
import { errors } from 'topcoder-react-lib';
1111
import Leaderboard from 'containers/tco/Leaderboard';
1212
import RecruitCRMJobs from 'containers/Gigs/RecruitCRMJobs';
13+
import EmailSubscribeForm from 'containers/EmailSubscribeForm';
14+
1315

1416
const { fireErrorMessage } = errors;
1517

@@ -34,6 +36,9 @@ export function AppComponentSwitch(appComponent) {
3436
if (appComponent.fields.type === 'RecruitCRM-Jobs') {
3537
return <RecruitCRMJobs {...appComponent.fields.props} key={appComponent.sys.id} />;
3638
}
39+
if (appComponent.fields.type === 'EmailSubscribeForm') {
40+
return <EmailSubscribeForm {...appComponent.fields.props} key={appComponent.sys.id} />;
41+
}
3742
fireErrorMessage('Unsupported app component type from contentful', '');
3843
return null;
3944
}
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/**
2+
* Genetic subscribe for MailChimp tags component
3+
*/
4+
import React from 'react';
5+
import PT from 'prop-types';
6+
import { isValidEmail } from 'utils/tc';
7+
import TextInput from 'components/GUIKit/TextInput';
8+
import _ from 'lodash';
9+
import LoadingIndicator from 'components/LoadingIndicator';
10+
import { Link } from 'topcoder-react-utils';
11+
import defaulTheme from './style.scss';
12+
13+
/* Holds the base URL of Community App endpoints that proxy HTTP request to
14+
* mailchimp APIs. */
15+
const PROXY_ENDPOINT = '/api/mailchimp';
16+
17+
class SubscribeMailChimpTagContainer extends React.Component {
18+
constructor(props) {
19+
super(props);
20+
this.state = {
21+
formErrors: {},
22+
formData: {},
23+
};
24+
this.onSubscribeClick = this.onSubscribeClick.bind(this);
25+
this.onFormInputChange = this.onFormInputChange.bind(this);
26+
this.validateForm = this.validateForm.bind(this);
27+
}
28+
29+
onSubscribeClick() {
30+
this.validateForm();
31+
// eslint-disable-next-line consistent-return
32+
this.setState((state) => {
33+
const { formData, formErrors } = state;
34+
if (_.isEmpty(formErrors)) {
35+
const { listId, tags } = this.props;
36+
const fetchUrl = `${PROXY_ENDPOINT}/${listId}/members/${formData.email}/tags`;
37+
const data = {
38+
email_address: formData.email,
39+
status: 'subscribed',
40+
tags: tags.map(t => ({ name: t, status: 'active' })),
41+
merge_fields: {
42+
FNAME: formData.fname,
43+
LNAME: formData.lname,
44+
},
45+
};
46+
fetch(fetchUrl, {
47+
method: 'POST',
48+
headers: {
49+
'Content-Type': 'application/json',
50+
},
51+
body: JSON.stringify(data),
52+
}).then(result => result.json()).then((dataResponse) => {
53+
if (dataResponse.status === 204) {
54+
// regist success
55+
return this.setState({
56+
subscribing: false,
57+
subsribed: true,
58+
error: '',
59+
});
60+
}
61+
if (dataResponse.status === 404) {
62+
// new email register it for list and add tags
63+
data.tags = tags;
64+
return fetch(`${PROXY_ENDPOINT}/${listId}/members`, {
65+
method: 'POST',
66+
headers: {
67+
'Content-Type': 'application/json',
68+
},
69+
body: JSON.stringify(data),
70+
})
71+
.then(result => result.json()).then((rsp) => {
72+
this.setState({
73+
subscribing: false,
74+
subsribed: !rsp.detail,
75+
error: rsp.detail ? rsp.title : '',
76+
});
77+
});
78+
}
79+
return this.setState({
80+
subscribing: false,
81+
subsribed: false,
82+
error: `Error ${dataResponse.status} when assigning tags to ${formData.email}`,
83+
});
84+
})
85+
.catch((e) => {
86+
this.setState({
87+
subscribing: false,
88+
subsribed: false,
89+
error: e.message,
90+
});
91+
});
92+
return { subscribing: true };
93+
}
94+
});
95+
}
96+
97+
onFormInputChange(key, val) {
98+
this.setState((state) => {
99+
const { formData } = state;
100+
formData[key] = val;
101+
return {
102+
...state,
103+
formData,
104+
};
105+
});
106+
this.validateForm(key);
107+
}
108+
109+
validateForm(key) {
110+
this.setState((state) => {
111+
const { formData, formErrors } = state;
112+
if (key) {
113+
// validate only the key
114+
if (!formData[key] || !_.trim(formData[key])) formErrors[key] = 'Required field';
115+
else if (key === 'email' && !(isValidEmail(formData.email))) formErrors.email = 'Invalid email';
116+
else delete formErrors[key];
117+
} else {
118+
_.each(['fname', 'lname', 'email'], (rkey) => {
119+
if (!formData[rkey] || !_.trim(formData[rkey])) formErrors[rkey] = 'Required field';
120+
else if (rkey === 'email' && !(isValidEmail(formData.email))) formErrors.email = 'Invalid email';
121+
else delete formErrors[key];
122+
});
123+
}
124+
// updated state
125+
return {
126+
...state,
127+
formErrors,
128+
};
129+
});
130+
}
131+
132+
render() {
133+
const {
134+
formData, formErrors, subscribing, subsribed, error,
135+
} = this.state;
136+
const {
137+
btnText, title, successTitle, successText, successLink, successLinkText,
138+
} = this.props;
139+
return (
140+
<div className={defaulTheme.wrapper}>
141+
{
142+
subscribing ? (
143+
<div className={defaulTheme.loadingWrap}>
144+
<LoadingIndicator />
145+
<p className={defaulTheme.loadingText}>
146+
Processing your subscription...
147+
</p>
148+
</div>
149+
) : null
150+
}
151+
{
152+
subsribed || error ? (
153+
<div className={defaulTheme.subscribedWrap}>
154+
<h4>{error ? 'OOPS!' : successTitle}</h4>
155+
<p className={error ? defaulTheme.errorMsg : null}>{error || successText}</p>
156+
{
157+
error
158+
? <button type="button" onClick={() => { window.location.reload(); }} className={defaulTheme.button}>TRY AGAIN</button>
159+
: <Link to={successLink} className={defaulTheme.button}>{successLinkText}</Link>
160+
}
161+
</div>
162+
) : null
163+
}
164+
{
165+
!subscribing && !subsribed && !error ? (
166+
<React.Fragment>
167+
<h6>{title}</h6>
168+
<TextInput
169+
placeholder="First Name"
170+
label="First Name"
171+
onChange={val => this.onFormInputChange('fname', val)}
172+
errorMsg={formErrors.fname}
173+
value={formData.fname}
174+
required
175+
/>
176+
<TextInput
177+
placeholder="Last Name"
178+
label="Last Name"
179+
onChange={val => this.onFormInputChange('lname', val)}
180+
errorMsg={formErrors.lname}
181+
value={formData.lname}
182+
required
183+
/>
184+
<TextInput
185+
placeholder="Email Address"
186+
label="Email Address"
187+
onChange={val => this.onFormInputChange('email', val)}
188+
errorMsg={formErrors.email}
189+
value={formData.email}
190+
required
191+
/>
192+
<button type="button" onClick={this.onSubscribeClick} disabled={!_.isEmpty(formErrors) || subscribing} className={defaulTheme.button}>{btnText}</button>
193+
</React.Fragment>
194+
) : null
195+
}
196+
</div>
197+
);
198+
}
199+
}
200+
201+
SubscribeMailChimpTagContainer.defaultProps = {
202+
title: '',
203+
btnText: '',
204+
successTitle: 'Success!',
205+
successText: '',
206+
successLink: '',
207+
successLinkText: '',
208+
};
209+
210+
SubscribeMailChimpTagContainer.propTypes = {
211+
listId: PT.string.isRequired,
212+
tags: PT.arrayOf(PT.string).isRequired,
213+
title: PT.string,
214+
btnText: PT.string,
215+
successTitle: PT.string,
216+
successText: PT.string,
217+
successLink: PT.string,
218+
successLinkText: PT.string,
219+
};
220+
221+
export default SubscribeMailChimpTagContainer;
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
@import "~components/Contentful/default";
2+
3+
.loadingWrap {
4+
margin: 0;
5+
padding: 0 34px;
6+
7+
@include gui-kit-headers;
8+
@include gui-kit-content;
9+
@include roboto-regular;
10+
11+
.loadingText {
12+
font-family: Roboto, sans-serif;
13+
color: #2a2a2a;
14+
text-align: center;
15+
margin-top: 26px;
16+
}
17+
}
18+
19+
.subscribedWrap {
20+
display: flex;
21+
flex-direction: column;
22+
align-items: center;
23+
padding: 0 34px;
24+
25+
@include gui-kit-headers;
26+
@include gui-kit-content;
27+
@include roboto-regular;
28+
29+
> h4 {
30+
margin-bottom: 16px !important;
31+
}
32+
33+
> p {
34+
font-size: 24px !important;
35+
line-height: 36px !important;
36+
text-align: center;
37+
38+
&.errorMsg {
39+
color: #ef476f;
40+
}
41+
}
42+
}
43+
44+
.wrapper {
45+
display: flex;
46+
flex-direction: column;
47+
color: #2a2a2a;
48+
49+
@include gui-kit-headers;
50+
@include gui-kit-content;
51+
@include roboto-regular;
52+
53+
h6 {
54+
margin-top: 41px;
55+
56+
@include xs-to-sm {
57+
margin-top: 0;
58+
}
59+
}
60+
61+
> div {
62+
margin-bottom: 8px;
63+
}
64+
65+
.button {
66+
background-color: #137d60;
67+
border-radius: 20px;
68+
color: #fff !important;
69+
font-size: 14px;
70+
font-weight: bolder;
71+
text-decoration: none;
72+
text-transform: uppercase;
73+
line-height: 40px;
74+
padding: 0 20px;
75+
border: none;
76+
outline: none;
77+
margin-top: 13px;
78+
margin-bottom: 41px;
79+
max-width: 150px;
80+
81+
&:hover {
82+
box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2);
83+
background-color: #0ab88a;
84+
}
85+
86+
@include xs-to-sm {
87+
margin-bottom: 20px;
88+
}
89+
90+
&:disabled {
91+
background-color: #e9e9e9 !important;
92+
border: none !important;
93+
text-decoration: none !important;
94+
color: #fafafb !important;
95+
box-shadow: none !important;
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)