Skip to content

Ref email tracking #5735

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 8 commits into from
Oct 7, 2021
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 @@ -349,14 +349,14 @@ workflows:
filters:
branches:
only:
- gig-apply-new-options-dropdown
- free
# This is alternate dev env for parallel testing
- "build-qa":
context : org-global
filters:
branches:
only:
- member-path-component
- ref-email-tracking
# This is beta env for production soft releases
- "build-prod-beta":
context : org-global
Expand Down
11 changes: 9 additions & 2 deletions src/server/routes/growsurf.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,26 @@
* The routes related to Growsurf integration
*/

import _ from 'lodash';
import express from 'express';
import { middleware } from 'tc-core-library-js';
import config from 'config';
import GrowsurfService from '../services/growsurf';

const cors = require('cors');

const authenticator = middleware.jwtAuthenticator;
const authenticatorOptions = _.pick(config.SECRET.JWT_AUTH, ['AUTH_SECRET', 'VALID_ISSUERS']);
const routes = express.Router();

// Enables CORS on those routes according config above
// ToDo configure CORS for set of our trusted domains
routes.use(cors());
routes.options('*', cors());

routes.get('/participants', (req, res) => new GrowsurfService().getParticipant(req, res).then(res.send.bind(res)));
routes.post('/participants', (req, res) => new GrowsurfService().getOrCreateParticipant(req, res).then(res.send.bind(res)));
routes.get('/participant/:emailOrId', (req, res) => new GrowsurfService().getParticipantController(req, res).then(res.send.bind(res)));
routes.get('/participants', (req, res) => new GrowsurfService().getParticipantsController(req, res).then(res.send.bind(res)));
routes.post('/participants', (req, res, next) => authenticator(authenticatorOptions)(req, res, next), (req, res) => new GrowsurfService().getOrCreateParticipantController(req, res).then(res.send.bind(res)));
routes.patch('/participant/:emailOrId', (req, res, next) => authenticator(authenticatorOptions)(req, res, next), (req, res) => new GrowsurfService().updateParticipantController(req, res).then(res.send.bind(res)));

export default routes;
46 changes: 37 additions & 9 deletions src/server/services/growsurf.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default class GrowsurfService {
}

/**
* Gets get participant.
* Gets participant by email or id
* @return {Promise}
* @param {String} idOrEmail growsurf id or email
*/
Expand All @@ -45,14 +45,28 @@ export default class GrowsurfService {
}

/**
* Gets get participant by email or id.
* Controller - Gets get participant by email or id
* @return {Promise}
* @param {Object} req the request.
* @param {Object} res the response.
*/
async getParticipant(req, res) {
const { participantId } = req.query;
const response = await fetch(`${this.private.baseUrl}/campaign/${config.GROWSURF_CAMPAIGN_ID}/participant/${participantId}`, {
async getParticipantController(req, res) {
const { emailOrId } = req.params;
const growSurfData = await this.getParticipantByIdOREmail(emailOrId);
if (growSurfData.error) {
res.status(growSurfData.code);
}
return growSurfData;
}

/**
* Controller - Gets get participants in the campaign
* @return {Promise}
* @param {Object} req the request.
* @param {Object} res the response.
*/
async getParticipantsController(req, res) {
const response = await fetch(`${this.private.baseUrl}/campaign/${config.GROWSURF_CAMPAIGN_ID}/participants`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Expand All @@ -64,7 +78,7 @@ export default class GrowsurfService {
return {
error: await response.json(),
code: response.status,
url: `${this.private.baseUrl}/campaign/${config.GROWSURF_CAMPAIGN_ID}/participant/${participantId}`,
url: `${this.private.baseUrl}/campaign/${config.GROWSURF_CAMPAIGN_ID}/participants`,
};
}
const data = await response.json();
Expand Down Expand Up @@ -98,13 +112,13 @@ export default class GrowsurfService {
}

/**
* Gets get participant by email or id
* Controller - Gets get participant by email or id
* if not exxists create it
* @return {Promise}
* @param {Object} req the request.
* @param {Object} res the response.
*/
async getOrCreateParticipant(req, res) {
async getOrCreateParticipantController(req, res) {
const { body } = req;
const result = await this.addParticipant(JSON.stringify({
email: body.email,
Expand Down Expand Up @@ -133,7 +147,7 @@ export default class GrowsurfService {
'Content-Type': 'application/json',
Authorization: this.private.authorization,
},
body,
body: JSON.stringify(body),
});
if (response.status >= 300) {
return {
Expand All @@ -146,4 +160,18 @@ export default class GrowsurfService {
const data = await response.json();
return data;
}

/**
* Controller - update participant in the system
* @param {Object} req request
* @param {Object} res respons
*/
async updateParticipantController(req, res) {
const { emailOrId } = req.params;
const updateGrowRes = await this.updateParticipant(emailOrId, req.body);
if (updateGrowRes.error) {
res.status(updateGrowRes.code);
}
return updateGrowRes;
}
}
5 changes: 4 additions & 1 deletion src/shared/actions/growSurf.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ function getReferralIdInit() {
/**
* Get referral id for the logged user
* if this member does not exist in growsurf it creates it in the system
* @param {Object} profile the member auth profile
* @param {String} token the auth token
*/
async function getReferralIdDone(profile) {
async function getReferralIdDone(profile, tokenV3) {
if (profile.email) {
const res = await fetch(`${PROXY_ENDPOINT}/growsurf/participants?participantId=${profile.email}`, {
method: 'POST',
Expand All @@ -31,6 +33,7 @@ async function getReferralIdDone(profile) {
}),
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${tokenV3}`,
},
});
if (res.status >= 300) {
Expand Down
7 changes: 5 additions & 2 deletions src/shared/components/Gigs/GigDetails/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -262,12 +262,15 @@ function GigDetails(props) {
&& (
<ReferralModal
profile={profile}
onCloseButton={() => setModalOpen(false)}
onCloseButton={() => {
onReferralDone();
setModalOpen(false);
}}
isReferrSucess={isReferrSucess}
isReferrError={isReferrError}
referralId={growSurf && growSurf.data ? growSurf.data.id : null}
onReferralDone={() => {
onReferralDone();
onReferralDone(true);
setModalOpen(false);
}}
/>
Expand Down
8 changes: 7 additions & 1 deletion src/shared/components/Gigs/ReferralModal/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,13 @@ function ReferralModal({
<div className={modalStyle.referrSucess}>
<h3 className={modalStyle.title}>OOPS!</h3>
<p className={modalStyle.loginMsg}>{isReferrError.message}</p>
<p>Looks like there is a problem on our end. Please try again.<br />If this persists please contact <a href="mailto:support@topcoder.com">support@topcoder.com</a>.</p>
{
isReferrError.userError ? (
<p>If you think this is an error please contact <a href="mailto:support@topcoder.com">support@topcoder.com</a>.</p>
) : (
<p>Looks like there is a problem on our end. Please try again.<br />If this persists please contact <a href="mailto:support@topcoder.com">support@topcoder.com</a>.</p>
)
}
<div className={modalStyle.ctaButtons}>
<PrimaryButton
onClick={onCloseButton}
Expand Down
15 changes: 15 additions & 0 deletions src/shared/components/Gigs/ReferralModal/modal.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* stylelint-disable no-descending-specificity */
@import "~styles/mixins";
@import "~components/Contentful/default";

Expand Down Expand Up @@ -105,10 +106,24 @@
padding: 50px 40px;
}

.loginMsg {
font-size: 20px;
}

.regTxt {
font-size: 14px;
margin: 10px 0 0;
}

.ctaButtons {
@include xs-to-sm {
flex-direction: column;

> button {
margin: 0 0 20px !important;
}
}
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/shared/components/MemberPath/PathSelector/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export default function PathSelector({
</div>
<a
href={data.items[activeItemIndex].btnURL}
target="_blank"
target={data.items[activeItemIndex].btnNewTab ? '_blank' : '_self'}
rel="noopener noreferrer"
className={styles.contentButton}
>
Expand Down
101 changes: 96 additions & 5 deletions src/shared/containers/Gigs/RecruitCRMJobDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,52 @@ ${config.URL.BASE}${config.GIGS_PAGES_PATH}/${props.id}`,
* Send gig referral invite
*/
async onSendClick(email) {
const { profile, growSurf } = this.props;
const {
profile, growSurf, tokenV3,
} = this.props;
const { formData } = this.state;
// email the invite
// should not be able to send emails to themselves
if (profile.email === email) {
this.setState({
isReferrError: {
message: 'You are not allowed to send to yourself.',
userError: true,
},
});
// exit no email sending
return;
}
// process sent log
let { emailInvitesLog, emailInvitesStatus } = growSurf.data.metadata;
if (!emailInvitesLog) emailInvitesLog = '';
// check if email is in sent log alredy?
const foundInLog = emailInvitesLog.indexOf(email);
if (foundInLog !== -1) {
this.setState({
isReferrError: {
message: `${email} was already invited.`,
userError: true,
},
});
// exit no email sending
return;
}
// check if email is already referred?
const growCheck = await fetch(`${PROXY_ENDPOINT}/growsurf/participant/${email}`);
if (growCheck.status === 200) {
const growCheckData = await growCheck.json();
if (growCheckData.referrer) {
this.setState({
isReferrError: {
message: `${email} has already been referred.`,
userError: true,
},
});
// exit no email sending
return;
}
}
// // email the invite
const res = await fetch(`${PROXY_ENDPOINT}/mailchimp/email`, {
method: 'POST',
body: JSON.stringify({
Expand All @@ -79,21 +122,66 @@ ${config.URL.BASE}${config.GIGS_PAGES_PATH}/${props.id}`,
this.setState({
isReferrError: await res.json(),
});
} else {
// exit no email tracking due to the error
return;
}
// parse the log to array of emails
if (emailInvitesLog.length) {
emailInvitesLog = emailInvitesLog.split(',');
} else emailInvitesLog = [];
// prepare growSurf update payload
// we keep only 10 emails in the log to justify program rules
if (emailInvitesLog.length < 10) {
emailInvitesLog.push(email);
}
// Auto change status when 10 emails sent
if (emailInvitesLog.length === 10 && emailInvitesStatus !== 'Paid' && emailInvitesStatus !== 'Payment Pending') {
emailInvitesStatus = 'Payment Pending';
}
// put the tracking update in growsurf
const updateRed = await fetch(`${PROXY_ENDPOINT}/growsurf/participant/${growSurf.data.id}`, {
method: 'PATCH',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${tokenV3}`,
},
body: JSON.stringify({
...growSurf.data,
metadata: {
...growSurf.data.metadata,
emailInvitesSent: Number(growSurf.data.metadata.emailInvitesSent || 0) + 1,
emailInvitesLog: emailInvitesLog.join(),
emailInvitesStatus,
},
}),
});
if (updateRed.status >= 300) {
this.setState({
isReferrSucess: true,
isReferrError: await updateRed.json(),
});
// exit no email tracking due to the error
// just notify the user about it
return;
}
// finally do:
this.setState({
isReferrSucess: true,
});
}

/**
* Reset the form when referral done
*/
onReferralDone() {
onReferralDone(refresh = false) {
if (refresh) {
window.location.reload(false);
return;
}
const { formData } = this.state;
delete formData.email;
this.setState({
isReferrSucess: false,
isReferrError: false,
formData,
});
}
Expand Down Expand Up @@ -143,6 +231,7 @@ RecruitCRMJobDetailsContainer.defaultProps = {
application: null,
profile: {},
growSurf: {},
tokenV3: null,
};

RecruitCRMJobDetailsContainer.propTypes = {
Expand All @@ -154,6 +243,7 @@ RecruitCRMJobDetailsContainer.propTypes = {
application: PT.shape(),
profile: PT.shape(),
growSurf: PT.shape(),
tokenV3: PT.string,
};

function mapStateToProps(state, ownProps) {
Expand All @@ -166,6 +256,7 @@ function mapStateToProps(state, ownProps) {
application: data ? data.application : null,
profile,
growSurf,
tokenV3: state.auth ? state.auth.tokenV3 : null,
};
}

Expand Down
Loading