- : challenges.map(challenge => (
+ : (challenges || []).map(challenge => (
- );
+ ) : null;
}
ChallengesFeed.defaultProps = {
challenges: [],
theme: 'light',
+ title: 'CHALLENGES',
+ challengeListingQuery: undefined,
};
ChallengesFeed.propTypes = {
challenges: PT.arrayOf(PT.shape()),
loading: PT.bool.isRequired,
theme: PT.oneOf(['dark', 'light']),
+ title: PT.string,
+ challengeListingQuery: PT.shape(),
};
diff --git a/src/shared/components/GUIKit/DropdownSkills/index.jsx b/src/shared/components/GUIKit/DropdownSkills/index.jsx
index a8f894aebb..d90405b006 100644
--- a/src/shared/components/GUIKit/DropdownSkills/index.jsx
+++ b/src/shared/components/GUIKit/DropdownSkills/index.jsx
@@ -90,7 +90,7 @@ function DropdownSkills({
className="dropdownContainer"
styleName={`container ${
selectedOption && !!selectedOption.length ? 'haveValue' : ''
- } ${errorMsg ? 'haveError' : ''} ${_.every(internalTerms, { selected: true }) ? 'isEmptySelectList' : ''}`}
+ } ${errorMsg ? 'haveError' : ''}`}
>
)
@@ -194,6 +265,8 @@ const ChallengeTab = ({
ChallengeTab.defaultProps = {
activeBucket: null,
selectBucket: () => {},
+ setFilterState: () => {},
+ filterState: {},
setPreviousBucketOfActiveTab: () => {},
setPreviousBucketOfPastChallengesTab: () => {},
previousBucketOfActiveTab: null,
@@ -212,6 +285,8 @@ ChallengeTab.propTypes = {
previousBucketOfActiveTab: PT.string,
selectBucket: PT.func,
previousBucketOfPastChallengesTab: PT.string,
+ setFilterState: PT.func,
+ filterState: PT.shape(),
};
export default ChallengeTab;
diff --git a/src/shared/components/challenge-listing/index.jsx b/src/shared/components/challenge-listing/index.jsx
index 85d65b32aa..7a4a6c3655 100644
--- a/src/shared/components/challenge-listing/index.jsx
+++ b/src/shared/components/challenge-listing/index.jsx
@@ -168,6 +168,8 @@ export default function ChallengeListing(props) {
previousBucketOfActiveTab={previousBucketOfActiveTab}
selectBucket={selectBucket}
location={location}
+ filterState={props.filterState}
+ setFilterState={props.setFilterState}
/>
diff --git a/src/shared/containers/Dashboard/ChallengesFeed.jsx b/src/shared/containers/Dashboard/ChallengesFeed.jsx
index 5dbbfd7e56..5e9ed2aef5 100644
--- a/src/shared/containers/Dashboard/ChallengesFeed.jsx
+++ b/src/shared/containers/Dashboard/ChallengesFeed.jsx
@@ -2,6 +2,7 @@
* ChallengesFeed component
*/
import React from 'react';
+import _ from 'lodash';
import PT from 'prop-types';
import ChallengesFeed from 'components/Dashboard/Challenges';
import { connect } from 'react-redux';
@@ -9,28 +10,57 @@ import actions from '../../actions/dashboard';
class ChallengesFeedContainer extends React.Component {
componentDidMount() {
- const { getChallenges, challenges, itemCount } = this.props;
+ const {
+ getChallenges, challenges, itemCount, tags,
+ includeAllTags, projectId, excludeTags, title, tracks,
+ } = this.props;
if (!challenges || challenges.length === 0) {
- getChallenges({
- page: 1,
- perPage: itemCount,
- types: ['CH', 'F2F', 'MM'],
- tracks: ['DES', 'DEV', 'DEV', 'DS', 'QA'],
- status: 'Active',
- sortBy: 'updated',
- sortOrder: 'desc',
- isLightweight: true,
- currentPhaseName: 'Registration',
- });
+ getChallenges(
+ title,
+ _.omitBy({
+ page: 1,
+ perPage: excludeTags && excludeTags.length ? undefined : itemCount,
+ types: ['CH', 'F2F', 'MM'],
+ tracks,
+ status: 'Active',
+ sortBy: 'updated',
+ sortOrder: 'desc',
+ isLightweight: true,
+ currentPhaseName: 'Registration',
+ tags: tags && tags.length ? tags : undefined,
+ includeAllTags: !!includeAllTags || undefined,
+ projectId: projectId || undefined,
+ }, _.isUndefined),
+ );
}
}
render() {
- const { challenges, theme, loading } = this.props;
+ const {
+ theme, loading, excludeTags, itemCount, title, challengeListingQuery,
+ } = this.props;
+ let { challenges } = this.props;
+
+ // this is a workaround for excluding challenges by tags
+ // there is no API support for this, so we have to do it manually
+ // in taht case we load more challenges, not limited to itemCount and filter out by tags
+ // default value for perPage is 20 when not specified
+ if (excludeTags && excludeTags.length) {
+ // filter out by excluded tags
+ challenges = challenges.filter(c => !c.tags.some(t => excludeTags.includes(t)));
+ // limit to itemCount
+ challenges = challenges.slice(0, itemCount);
+ }
return (
-
+
);
}
}
@@ -40,6 +70,13 @@ ChallengesFeedContainer.defaultProps = {
challenges: [],
loading: true,
theme: 'light',
+ tags: [],
+ includeAllTags: false,
+ projectId: null,
+ excludeTags: [],
+ title: 'CHALLENGES',
+ challengeListingQuery: undefined,
+ tracks: ['DES', 'DEV', 'DS', 'QA'],
};
ChallengesFeedContainer.propTypes = {
@@ -48,19 +85,35 @@ ChallengesFeedContainer.propTypes = {
getChallenges: PT.func.isRequired,
loading: PT.bool,
theme: PT.oneOf(['dark', 'light']),
+ tags: PT.arrayOf(PT.string),
+ includeAllTags: PT.bool,
+ projectId: PT.number,
+ excludeTags: PT.arrayOf(PT.string),
+ title: PT.string,
+ challengeListingQuery: PT.shape(),
+ tracks: PT.arrayOf(PT.string),
};
-const mapStateToProps = state => ({
- challenges: state.dashboard.challenges,
- loading: state.dashboard.loading,
-});
+function mapStateToProps(state, ownProps) {
+ const { dashboard } = state;
+ const id = ownProps.title || 'CHALLENGES';
+
+ if (dashboard[id]) {
+ return {
+ challenges: dashboard[id].challenges,
+ loading: dashboard[id].loading,
+ };
+ }
+
+ return state;
+}
const mapDispatchToProps = dispatch => ({
- getChallenges: (query) => {
+ getChallenges: (title, query) => {
const a = actions.dashboard;
- dispatch(a.fetchChallengesInit());
- dispatch(a.fetchChallengesDone(query));
+ dispatch(a.fetchChallengesInit(title));
+ dispatch(a.fetchChallengesDone(title, query));
},
});
diff --git a/src/shared/containers/Dashboard/index.jsx b/src/shared/containers/Dashboard/index.jsx
index 1997e25331..085d98a129 100644
--- a/src/shared/containers/Dashboard/index.jsx
+++ b/src/shared/containers/Dashboard/index.jsx
@@ -24,11 +24,16 @@ import darkTheme from './themes/dark.scss';
const THEMES = {
dark: darkTheme,
};
+const { INNOVATION_CHALLENGES_TAG } = config;
function SlashTCContainer(props) {
const theme = THEMES.dark; // for v1 only dark theme
const isTabletOrMobile = useMediaQuery({ maxWidth: 768 });
const title = 'Home | Topcoder';
+ const challengeListingQuery = {
+ search: INNOVATION_CHALLENGES_TAG,
+ isInnovationChallenge: true,
+ };
useEffect(() => {
if (props.tokenV3 && !isTokenExpired(props.tokenV3)) return;
@@ -49,7 +54,15 @@ function SlashTCContainer(props) {
-
+
+
@@ -70,7 +83,15 @@ function SlashTCContainer(props) {
{/* Center column */}
-
+
+
diff --git a/src/shared/reducers/challenge-listing/index.js b/src/shared/reducers/challenge-listing/index.js
index 98c3d390e8..add5ae8941 100644
--- a/src/shared/reducers/challenge-listing/index.js
+++ b/src/shared/reducers/challenge-listing/index.js
@@ -407,7 +407,7 @@ function onSetFilter(state, { payload }) {
* do it very carefuly (many params are not validated). */
const filter = _.pickBy(_.pick(
payload,
- ['tags', 'types', 'search', 'startDateEnd', 'endDateStart', 'groups', 'events', 'tracks', 'tco'],
+ ['tags', 'types', 'search', 'startDateEnd', 'endDateStart', 'groups', 'events', 'tracks', 'tco', 'isInnovationChallenge'],
), value => (!_.isArray(value) && value && value !== '') || (_.isArray(value) && value.length > 0));
const emptyArrayAllowedFields = ['types'];
diff --git a/src/shared/reducers/dashboard.js b/src/shared/reducers/dashboard.js
index c0fe816909..d04254c0f3 100644
--- a/src/shared/reducers/dashboard.js
+++ b/src/shared/reducers/dashboard.js
@@ -5,6 +5,17 @@
import actions from 'actions/dashboard';
import { redux } from 'topcoder-react-utils';
+function onInit(state, { payload }) {
+ return {
+ ...state,
+ [payload]: {
+ details: null,
+ failed: false,
+ loading: true,
+ },
+ };
+}
+
/**
* Handles done actions.
* @param {Object} state Previous state.
@@ -13,9 +24,11 @@ import { redux } from 'topcoder-react-utils';
function onDone(state, action) {
return {
...state,
- challenges: action.error ? null : action.payload,
- failed: action.error,
- loading: false,
+ [action.payload.title]: {
+ challenges: action.error ? null : action.payload.challenges,
+ failed: action.error,
+ loading: false,
+ },
};
}
@@ -26,14 +39,7 @@ function onDone(state, action) {
*/
function create(initialState) {
return redux.handleActions({
- [actions.dashboard.fetchChallengesInit](state) {
- return {
- ...state,
- details: null,
- failed: false,
- loading: true,
- };
- },
+ [actions.dashboard.fetchChallengesInit]: onInit,
[actions.dashboard.fetchChallengesDone]: onDone,
}, initialState || {});
}
diff --git a/src/shared/utils/challenge-listing/buckets.js b/src/shared/utils/challenge-listing/buckets.js
index ca85e0572a..18e2c7faaa 100644
--- a/src/shared/utils/challenge-listing/buckets.js
+++ b/src/shared/utils/challenge-listing/buckets.js
@@ -195,6 +195,7 @@ export function filterChanged(filter, prevFilter) {
}
return (!_.isEqual(filter.tracks, prevFilter.tracks))
|| (filter.search !== prevFilter.search)
+ || (filter.isInnovationChallenge !== prevFilter.isInnovationChallenge)
|| (filter.tco !== prevFilter.tco)
|| (filter.startDateEnd !== prevFilter.startDateEnd)
|| (filter.endDateStart !== prevFilter.endDateStart)