diff --git a/src/shared/components/challenge-listing/Listing/Bucket/index.jsx b/src/shared/components/challenge-listing/Listing/Bucket/index.jsx
index fa4034c6c4..afcf67b309 100644
--- a/src/shared/components/challenge-listing/Listing/Bucket/index.jsx
+++ b/src/shared/components/challenge-listing/Listing/Bucket/index.jsx
@@ -9,7 +9,7 @@ import PT from 'prop-types';
// import qs from 'qs';
import React, { useRef } from 'react';
// import { config } from 'topcoder-react-utils';
-import Sort from 'utils/challenge-listing/sort';
+import Sort, { SORTS } from 'utils/challenge-listing/sort';
import {
NO_LIVE_CHALLENGES_CONFIG, BUCKETS, BUCKET_DATA, isRecommendedChallengeType,
} from 'utils/challenge-listing/buckets';
@@ -54,7 +54,7 @@ export default function Bucket({
setSearchText,
}) {
const activeBucketData = isRecommendedChallengeType(bucket, filterState)
- ? BUCKET_DATA[bucket].sorts : BUCKET_DATA[bucket].sorts.filter(item => item !== 'bestMatch');
+ ? [SORTS.BEST_MATCH] : BUCKET_DATA[bucket].sorts.filter(item => item !== 'bestMatch');
const refs = useRef([]);
refs.current = [];
diff --git a/src/shared/components/challenge-listing/Listing/index.jsx b/src/shared/components/challenge-listing/Listing/index.jsx
index 946fa6e5c3..1c2c1d47b9 100644
--- a/src/shared/components/challenge-listing/Listing/index.jsx
+++ b/src/shared/components/challenge-listing/Listing/index.jsx
@@ -224,7 +224,6 @@ function Listing({
//
// );
// }
-
const loading = loadingMyChallenges
|| loadingMyPastChallenges
|| loadingOpenForRegistrationChallenges
diff --git a/src/shared/components/challenge-listing/Listing/style.scss b/src/shared/components/challenge-listing/Listing/style.scss
index 961c7c65bb..937bf4286b 100644
--- a/src/shared/components/challenge-listing/Listing/style.scss
+++ b/src/shared/components/challenge-listing/Listing/style.scss
@@ -2,12 +2,12 @@
.challengeCardContainer {
border-radius: $corner-radius;
- width: 100%;
- padding: 12px $base-unit 0 $base-unit;
+ width: 70%;
+ padding: 0 $base-unit;
@include xs-to-md {
width: 100%;
- padding: 12px $base-unit + 5 0 $base-unit + 5;
+ padding: 0 $base-unit + 5;
}
.category-challenges-container {
diff --git a/src/shared/components/challenge-listing/NoChallengeCard/style.scss b/src/shared/components/challenge-listing/NoChallengeCard/style.scss
index 88bc35b813..0cf45ef169 100644
--- a/src/shared/components/challenge-listing/NoChallengeCard/style.scss
+++ b/src/shared/components/challenge-listing/NoChallengeCard/style.scss
@@ -1,6 +1,8 @@
@import '~styles/mixins';
.container {
+ width: 70%;
+ height: 150px;
background-color: #fff;
border-radius: 4px;
display: flex;
diff --git a/src/shared/components/challenge-listing/index.jsx b/src/shared/components/challenge-listing/index.jsx
index 7f089733b3..7671f8cde4 100644
--- a/src/shared/components/challenge-listing/index.jsx
+++ b/src/shared/components/challenge-listing/index.jsx
@@ -99,74 +99,74 @@ export default function ChallengeListing(props) {
// );
// } else {
const challengeCardContainer = (
-
- {
- filterState.recommended
- && !loadingOpenForRegistrationChallenges
- && activeBucket === 'openForRegistration'
- && !openForRegistrationChallenges.length && (
-
- )
- }
-
-
-
+ isLoggedIn={isLoggedIn}
+ setSearchText={setSearchText}
+ />
);
- // }
const desktop = useMediaQuery({ minWidth: 1024 });
+ const isRecommendedOn = filterState.recommended
+ && !loadingOpenForRegistrationChallenges
+ && activeBucket === 'openForRegistration'
+ && !openForRegistrationChallenges.length;
return (
+ {
+ filterState.recommended
+ ? null
+ : (
+
+ )
+ }
+
- {challengeCardContainer}
+ {
+ isRecommendedOn
+ ?
+ : challengeCardContainer
+ }
diff --git a/src/shared/components/challenge-listing/style.scss b/src/shared/components/challenge-listing/style.scss
index 741ab8bfb0..3e312335dd 100644
--- a/src/shared/components/challenge-listing/style.scss
+++ b/src/shared/components/challenge-listing/style.scss
@@ -98,15 +98,6 @@ $challenge-radius-4: $corner-radius * 2;
}
}
-.card-container {
- width: 70%;
-
- @include xs-to-md {
- width: 100%;
- padding-top: 0;
- }
-}
-
.recommended-plus-tag {
margin-left: 3px;
display: inline-block;
diff --git a/src/shared/containers/EmailSubscribeForm/index.jsx b/src/shared/containers/EmailSubscribeForm/index.jsx
index 5b9fe1d654..e041e06387 100644
--- a/src/shared/containers/EmailSubscribeForm/index.jsx
+++ b/src/shared/containers/EmailSubscribeForm/index.jsx
@@ -32,25 +32,20 @@ class SubscribeMailChimpTagContainer extends React.Component {
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 { listId, interests } = this.props;
+ const fetchUrl = `${PROXY_ENDPOINT}/${listId}/members/${formData.email}`;
const data = {
- email_address: formData.email,
status: 'subscribed',
- tags: tags.map(t => ({ name: t, status: 'active' })),
- merge_fields: {
- FNAME: formData.fname,
- LNAME: formData.lname,
- },
+ interests,
};
fetch(fetchUrl, {
- method: 'POST',
+ method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
}).then(result => result.json()).then((dataResponse) => {
- if (dataResponse.status === 204) {
+ if (dataResponse.status === 'subscribed') {
// regist success
return this.setState({
subscribing: false,
@@ -58,15 +53,21 @@ class SubscribeMailChimpTagContainer extends React.Component {
error: '',
});
}
- if (dataResponse.status === 404) {
+ if (dataResponse.status === 400) {
// 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),
+ body: JSON.stringify({
+ ...data,
+ email_address: formData.email,
+ merge_fields: {
+ FNAME: formData.fname,
+ LNAME: formData.lname,
+ },
+ }),
})
.then(result => result.json()).then((rsp) => {
this.setState({
@@ -79,7 +80,7 @@ class SubscribeMailChimpTagContainer extends React.Component {
return this.setState({
subscribing: false,
subsribed: false,
- error: `Error ${dataResponse.status} when assigning tags to ${formData.email}`,
+ error: `Error ${dataResponse.status} when subscribing ${formData.email}`,
});
})
.catch((e) => {
@@ -209,7 +210,7 @@ SubscribeMailChimpTagContainer.defaultProps = {
SubscribeMailChimpTagContainer.propTypes = {
listId: PT.string.isRequired,
- tags: PT.arrayOf(PT.string).isRequired,
+ interests: PT.shape().isRequired,
title: PT.string,
btnText: PT.string,
successTitle: PT.string,
diff --git a/src/shared/containers/GSheet/index.jsx b/src/shared/containers/GSheet/index.jsx
new file mode 100644
index 0000000000..591ee4c3c9
--- /dev/null
+++ b/src/shared/containers/GSheet/index.jsx
@@ -0,0 +1,64 @@
+/**
+ * Google sheets container to load data from sheets
+ * and render it as table
+ */
+import React from 'react';
+import PT from 'prop-types';
+import { connect } from 'react-redux';
+import { isEmpty } from 'lodash';
+import actions from 'actions/gSheet';
+import LoadingIndicator from 'components/LoadingIndicator';
+import GSheet from 'components/GSheet';
+
+class GSeetContainer extends React.Component {
+ componentDidMount() {
+ const {
+ id, index, sheet, getGSheet,
+ } = this.props;
+ if (isEmpty(sheet)) {
+ getGSheet(id, index);
+ }
+ }
+
+ render() {
+ const { sheet, config } = this.props;
+ return isEmpty(sheet) ?
:
;
+ }
+}
+
+GSeetContainer.defaultProps = {
+ index: 0,
+ config: {},
+ sheet: null,
+};
+
+GSeetContainer.propTypes = {
+ id: PT.string.isRequired,
+ index: PT.number,
+ sheet: PT.shape(),
+ getGSheet: PT.func.isRequired,
+ config: PT.shape(),
+};
+
+function mapStateToProps(state, props) {
+ const { id, index } = props;
+ return {
+ id: props.id,
+ sheet: state.gSheet ? state.gSheet[`${id}-${index === undefined ? 0 : index}`] : {},
+ };
+}
+
+function mapDispatchToProps(dispatch) {
+ const a = actions.gsheets;
+ return {
+ getGSheet: (id, index) => {
+ dispatch(a.getGsheetInit(id, index));
+ dispatch(a.getGsheetDone(id, index));
+ },
+ };
+}
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(GSeetContainer);
diff --git a/src/shared/containers/NewsletterPreferences.jsx b/src/shared/containers/NewsletterPreferences.jsx
index acde23fc44..18e1ae1063 100644
--- a/src/shared/containers/NewsletterPreferences.jsx
+++ b/src/shared/containers/NewsletterPreferences.jsx
@@ -20,7 +20,7 @@ class NewsletterPreferencesContainer extends React.Component {
render() {
const {
- loading, error, preferences, saveEmailPreferences, email, updated,
+ loading, error, preferences, saveEmailPreferences, email, updated, status,
} = this.props;
if (loading || !preferences) return
;
if (error) {
@@ -32,6 +32,7 @@ class NewsletterPreferencesContainer extends React.Component {
preferences={preferences}
saveEmailPreferences={saveEmailPreferences}
updated={updated}
+ status={status}
/>
);
}
@@ -42,6 +43,7 @@ NewsletterPreferencesContainer.defaultProps = {
error: null,
preferences: null,
updated: null,
+ status: null,
};
NewsletterPreferencesContainer.propTypes = {
@@ -52,6 +54,7 @@ NewsletterPreferencesContainer.propTypes = {
preferences: PT.shape(),
email: PT.string.isRequired,
updated: PT.shape(),
+ status: PT.string,
};
function mapStateToProps(state) {
@@ -61,6 +64,7 @@ function mapStateToProps(state) {
loading: newsletterPreferences.loading,
error: newsletterPreferences.error,
preferences: newsletterPreferences.preferences,
+ status: newsletterPreferences.status,
updated: newsletterPreferences.updated,
};
}
diff --git a/src/shared/containers/NewsletterSignupForMembers.jsx b/src/shared/containers/NewsletterSignupForMembers.jsx
index 922eca698d..d3920ce72c 100644
--- a/src/shared/containers/NewsletterSignupForMembers.jsx
+++ b/src/shared/containers/NewsletterSignupForMembers.jsx
@@ -31,11 +31,11 @@ class NewsletterSignupForMembersContainer extends React.Component {
// Get interestIds and interest request object for mailchimp api
// to use in checkSubscription and subscribe function
- const { tags } = props;
- this.tagsIds = null;
- if (tags !== '') {
- this.tagsIds = tags.split(/ *, */);
- this.tagsIds[this.tagsIds.length - 1] = this.tagsIds[this.tagsIds.length - 1].replace(/^\s+|\s+$/g, '');
+ const { groups } = props;
+ this.groupsIds = null;
+ if (groups !== '') {
+ this.groupsIds = groups.split(/ *, */);
+ this.groupsIds[this.groupsIds.length - 1] = this.groupsIds[this.groupsIds.length - 1].replace(/^\s+|\s+$/g, '');
}
this.isSubscribed = false;
@@ -89,9 +89,9 @@ class NewsletterSignupForMembersContainer extends React.Component {
.then((dataResponse) => {
if (dataResponse.status === 'subscribed') {
this.isSubscribed = true;
- const subscribedTags = dataResponse.tags.map(t => t.name);
+ const subscribedTags = _.keys(_.pickBy(dataResponse.interests, v => v));
if (subscribedTags.length) {
- if (_.intersection(subscribedTags, this.tagsIds).length) {
+ if (_.intersection(subscribedTags, this.groupsIds).length) {
this.setState({ signupState: SIGNUP_NEWSLETTER.HIDDEN });
}
} else {
@@ -108,7 +108,7 @@ class NewsletterSignupForMembersContainer extends React.Component {
listId, user,
} = this.props;
- const fetchUrl = `${PROXY_ENDPOINT}/${listId}/members/${this.emailHash}/tags`;
+ const fetchUrl = `${PROXY_ENDPOINT}/${listId}/members/${this.emailHash}`;
let data = {};
if (!this.isSubscribed) {
@@ -122,18 +122,24 @@ class NewsletterSignupForMembersContainer extends React.Component {
};
}
- if (this.tagsIds) data.tags = this.tagsIds.map(t => ({ name: t, status: 'active' }));
+ if (this.groupsIds) {
+ data.interests = {};
+ // eslint-disable-next-line array-callback-return
+ this.groupsIds.map((group) => {
+ data.interests[group] = true;
+ });
+ }
const formData = JSON.stringify(data);
// use proxy for avoid 'Access-Control-Allow-Origin' bug
await fetch(fetchUrl, {
- method: 'POST',
+ method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: formData,
}).then(result => result.json()).then((dataResponse) => {
- if (dataResponse.status === 204) {
+ if (dataResponse.status === 'subscribed') {
// regist success
this.setState({ signupState: SIGNUP_NEWSLETTER.SIGNEDUP });
} else {
@@ -183,7 +189,7 @@ class NewsletterSignupForMembersContainer extends React.Component {
NewsletterSignupForMembersContainer.defaultProps = {
token: '',
label: 'Subscribe for Newsletter',
- tags: '',
+ groups: '',
user: null,
buttonTheme: 'primary-green-md',
title: 'Sign up for the Topcoder Newsletter',
@@ -194,7 +200,7 @@ NewsletterSignupForMembersContainer.propTypes = {
authenticating: PT.bool.isRequired,
token: PT.string,
label: PT.string,
- tags: PT.string,
+ groups: PT.string,
listId: PT.string.isRequired,
user: PT.shape(),
buttonTheme: PT.string,
diff --git a/src/shared/reducers/gSheet.js b/src/shared/reducers/gSheet.js
new file mode 100644
index 0000000000..31410bb31d
--- /dev/null
+++ b/src/shared/reducers/gSheet.js
@@ -0,0 +1,44 @@
+/**
+ * Reducer for state.gSheet
+ */
+import actions from 'actions/gSheet';
+import { handleActions } from 'redux-actions';
+
+/**
+ * Handles getMmleaderboardInit action.
+ * @param {Object} state Previous state.
+ */
+function onInit(state, { payload }) {
+ return {
+ ...state,
+ [`${payload.id}-${payload.index}`]: {},
+ };
+}
+
+/**
+ * Handles getMmleaderboardDone action.
+ * @param {Object} state Previous state.
+ * @param {Object} action The action.
+ */
+function onDone(state, { payload }) {
+ return {
+ ...state,
+ [`${payload.id}-${payload.index}`]: payload.data,
+ };
+}
+
+/**
+ * Creates mmleaderboard reducer with the specified initial state.
+ * @param {Object} state Optional. If not given, the default one is
+ * generated automatically.
+ * @return {Function} Reducer.
+ */
+function create(state = {}) {
+ return handleActions({
+ [actions.gsheets.getGsheetInit]: onInit,
+ [actions.gsheets.getGsheetDone]: onDone,
+ }, state);
+}
+
+/* Reducer with the default initial state. */
+export default create();
diff --git a/src/shared/reducers/index.js b/src/shared/reducers/index.js
index 758a8f6d6d..518f7de831 100644
--- a/src/shared/reducers/index.js
+++ b/src/shared/reducers/index.js
@@ -38,6 +38,7 @@ import { factory as termsFactory } from './terms';
import newsletterPreferences from './newsletterPreferences';
import mmLeaderboard from './mmLeaderboard';
import recruitCRM from './recruitCRM';
+import gSheet from './gSheet';
/**
* Given HTTP request, generates options for SSR by topcoder-react-lib's reducer
@@ -144,6 +145,7 @@ export function factory(req) {
newsletterPreferences,
recruitCRM,
mmLeaderboard,
+ gSheet,
}));
}
diff --git a/src/shared/reducers/newsletterPreferences.js b/src/shared/reducers/newsletterPreferences.js
index f1652090f2..6dab15735d 100644
--- a/src/shared/reducers/newsletterPreferences.js
+++ b/src/shared/reducers/newsletterPreferences.js
@@ -14,7 +14,8 @@ function onInit(state, { payload }) {
return {
...state,
email: payload,
- preferences: [],
+ preferences: {},
+ status: null,
loading: true,
};
}
@@ -25,15 +26,10 @@ function onInit(state, { payload }) {
* @param {Object} action The action.
*/
function onDone(state, { payload }) {
- const preferences = {};
- if (payload.preferences) {
- payload.preferences.forEach((record) => {
- preferences[record.name] = { ...record, checked: true };
- });
- }
return {
...state,
- preferences: payload.error ? null : preferences,
+ preferences: payload.error ? null : payload.preferences,
+ status: payload.error ? null : payload.status,
error: payload.error,
loading: false,
};
@@ -48,7 +44,7 @@ function onUpdateTagInit(state) {
function onUpdateTagDone(state, { payload }) {
// eslint-disable-next-line no-param-reassign
- state.preferences[payload.id] = { name: payload.id, checked: payload.checked };
+ state.preferences[payload.id] = payload.checked;
return {
...state,
updated: payload,
diff --git a/src/shared/services/gSheet.js b/src/shared/services/gSheet.js
new file mode 100644
index 0000000000..56355fe4e2
--- /dev/null
+++ b/src/shared/services/gSheet.js
@@ -0,0 +1,22 @@
+import fetch from 'isomorphic-fetch';
+import { logger } from 'topcoder-react-lib';
+
+const PROXY_ENDPOINT = '/api/gsheets';
+
+export default class Service {
+ baseUrl = PROXY_ENDPOINT;
+
+ /**
+ * Get gsheet by id
+ * @param {string} id The sheet id
+ * @param {number} index sheet index
+ */
+ async getSheet(id, index) {
+ const res = await fetch(`${this.baseUrl}/${id}${index !== undefined ? `?index=${index}` : ''}`);
+ if (!res.ok) {
+ const error = new Error(`Failed to get gsheet ${id}`);
+ logger.error(error, res);
+ }
+ return res.json();
+ }
+}
diff --git a/src/shared/utils/challenge-listing/helper.js b/src/shared/utils/challenge-listing/helper.js
index f88506e2ae..bf349689a4 100644
--- a/src/shared/utils/challenge-listing/helper.js
+++ b/src/shared/utils/challenge-listing/helper.js
@@ -39,5 +39,5 @@ export function phaseStartDate(phase) {
* @param {Float} score
*/
export function calculateScore(score) {
- return Math.trunc((parseFloat(score) + 1.0) / 2.0 * 100.0);
+ return Math.trunc(parseFloat(score) * 100.0);
}
diff --git a/src/styles/awesome.css b/src/styles/awesome.css
index ee69542f8e..c0dc23bb1e 100644
--- a/src/styles/awesome.css
+++ b/src/styles/awesome.css
@@ -65,10 +65,6 @@
content: "\f110";
}
-.fa-info-cirle::before {
- content: "\0024D8";
-}
-
@keyframes fa-spin {
0% {
-webkit-transform: rotate(0deg);