diff --git a/config/constants/development.js b/config/constants/development.js index 15478e88..f23bbfe6 100644 --- a/config/constants/development.js +++ b/config/constants/development.js @@ -12,6 +12,7 @@ module.exports = { CHALLENGE_TIMELINE_TEMPLATES_URL: `${DEV_API_HOSTNAME}/v5/timeline-templates`, CHALLENGE_TYPES_URL: `${DEV_API_HOSTNAME}/v5/challenge-types`, CHALLENGE_PHASES_URL: `${DEV_API_HOSTNAME}/v5/challenge-phases`, + CHALLENGE_TIMELINES_URL: `${DEV_API_HOSTNAME}/v5/challenge-timelines`, PROJECT_API_URL: `${DEV_API_HOSTNAME}/v5/projects`, GROUPS_API_URL: `${DEV_API_HOSTNAME}/v5/groups`, TERMS_API_URL: `${DEV_API_HOSTNAME}/v5/terms`, @@ -23,5 +24,6 @@ module.exports = { DIRECT_PROJECT_URL: `https://www.${DOMAIN}/direct`, ONLINE_REVIEW_URL: `https://software.${DOMAIN}`, DEFAULT_TERM_UUID: 'ae6fc4ff-3bd1-4e3f-a987-cc60ab94b422', - DEFAULT_NDA_UUID: '7245bb7d-d7c9-45a0-9603-d5ff05af0977' + DEFAULT_NDA_UUID: '7245bb7d-d7c9-45a0-9603-d5ff05af0977', + SUBMITTER_ROLE_UUID: '732339e7-8e30-49d7-9198-cccf9451e221' } diff --git a/config/constants/production.js b/config/constants/production.js index 2d0c3530..1f46e954 100644 --- a/config/constants/production.js +++ b/config/constants/production.js @@ -12,6 +12,7 @@ module.exports = { CHALLENGE_TIMELINE_TEMPLATES_URL: `${PROD_API_HOSTNAME}/v5/timeline-templates`, CHALLENGE_TYPES_URL: `${PROD_API_HOSTNAME}/v5/challenge-types`, CHALLENGE_PHASES_URL: `${PROD_API_HOSTNAME}/v5/challenge-phases`, + CHALLENGE_TIMELINES_URL: `${DEV_API_HOSTNAME}/v5/challenge-timelines`, PROJECT_API_URL: `${PROD_API_HOSTNAME}/v5/projects`, GROUPS_API_URL: `${PROD_API_HOSTNAME}/v5/groups`, TERMS_API_URL: `${PROD_API_HOSTNAME}/v5/terms`, @@ -22,5 +23,6 @@ module.exports = { CONNECT_APP_URL: `https://connect.${DOMAIN}`, ONLINE_REVIEW_URL: `https://software.${DOMAIN}`, DEFAULT_TERM_UUID: 'ae6fc4ff-3bd1-4e3f-a987-cc60ab94b422', - DEFAULT_NDA_UUID: '7245bb7d-d7c9-45a0-9603-d5ff05af0977' + DEFAULT_NDA_UUID: '7245bb7d-d7c9-45a0-9603-d5ff05af0977', + SUBMITTER_ROLE_UUID: '732339e7-8e30-49d7-9198-cccf9451e221' } diff --git a/package-lock.json b/package-lock.json index 46facb70..5c6ddc0f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3220,14 +3220,12 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "optional": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "optional": true, "requires": { "is-extglob": "^2.1.1" } @@ -3241,8 +3239,7 @@ "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "optional": true + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "to-regex-range": { "version": "5.0.1", @@ -6148,8 +6145,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -6167,13 +6163,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6186,18 +6180,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -6300,8 +6291,7 @@ }, "inherits": { "version": "2.0.4", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -6311,7 +6301,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6324,20 +6313,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "1.2.5", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.9.0", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -6354,7 +6340,6 @@ "mkdirp": { "version": "0.5.3", "bundled": true, - "optional": true, "requires": { "minimist": "^1.2.5" } @@ -6410,8 +6395,7 @@ }, "npm-normalize-package-bin": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "npm-packlist": { "version": "1.4.8", @@ -6436,8 +6420,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -6447,7 +6430,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -6516,8 +6498,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -6547,7 +6528,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6565,7 +6545,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6604,13 +6583,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.1.1", - "bundled": true, - "optional": true + "bundled": true } } }, @@ -10411,8 +10388,7 @@ "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "optional": true + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" }, "pify": { "version": "2.3.0", @@ -17118,9 +17094,9 @@ "angular-cookies": "^1.5.1", "angular-material": "^1.1.21", "angular-messages": "^1.5.2", - "appirio-tech-ng-iso-constants": "github:appirio-tech/ng-iso-constants#v1.0.7", + "appirio-tech-ng-iso-constants": "github:appirio-tech/ng-iso-constants#d8466ab76828208ccdaaeb10816a3f35cd59c39b", "appirio-tech-ng-ui-components": "^2.2.4", - "appirio-tech-react-components": "github:appirio-tech/react-components#feature/connectv2", + "appirio-tech-react-components": "github:appirio-tech/react-components#a471d4f9d1a4cd5a1a2f53aea3d1cc5dd6d78aea", "auth0-js": "^9.6.1", "babel-polyfill": "^6.7.4", "filestack-js": "^1.13.2", @@ -17206,7 +17182,8 @@ }, "acorn": { "version": "5.7.1", - "resolved": "" + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.1.tgz", + "integrity": "sha512-d+nbxBUGKg7Arpsvbnlq61mc12ek3EY8EQldM3GPAhWJ1UVxC6TDGbIvUMNU6obBX3i1+ptCIzV4vq0gFPEGVQ==" }, "acorn-globals": { "version": "1.0.9", @@ -17646,7 +17623,7 @@ "react-textarea-autosize": "^5.2.1", "react-transition-group": "^2.2.1", "redux-thunk": "^2.1.0", - "tc-ui": "git+https://github.com/appirio-tech/tc-ui.git#feature/connectv2", + "tc-ui": "git+https://github.com/appirio-tech/tc-ui.git#e577a0e704136f1e9ecce92ce4c0626aab932691", "uncontrollable": "^4.0.1" }, "dependencies": { @@ -20685,7 +20662,8 @@ }, "extend": { "version": "3.0.1", - "resolved": "" + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", + "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" }, "extglob": { "version": "0.3.2", @@ -21071,8 +21049,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": false, - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "optional": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "1.2.0", @@ -21093,14 +21070,12 @@ "balanced-match": { "version": "1.0.0", "resolved": false, - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "optional": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -21115,20 +21090,17 @@ "code-point-at": { "version": "1.1.0", "resolved": false, - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "optional": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "concat-map": { "version": "0.0.1", "resolved": false, - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "optional": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", "resolved": false, - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "optional": true + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", @@ -21245,8 +21217,7 @@ "inherits": { "version": "2.0.3", "resolved": false, - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "optional": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", @@ -21258,7 +21229,6 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -21273,7 +21243,6 @@ "version": "3.0.4", "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -21281,14 +21250,12 @@ "minimist": { "version": "0.0.8", "resolved": false, - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "optional": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { "version": "2.3.5", "resolved": false, "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -21307,7 +21274,6 @@ "version": "0.5.1", "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "optional": true, "requires": { "minimist": "0.0.8" } @@ -21394,8 +21360,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": false, - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "optional": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "object-assign": { "version": "4.1.1", @@ -21407,7 +21372,6 @@ "version": "1.4.0", "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "optional": true, "requires": { "wrappy": "1" } @@ -21493,8 +21457,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": false, - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", @@ -21530,7 +21493,6 @@ "version": "1.0.2", "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -21550,7 +21512,6 @@ "version": "3.0.1", "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -21594,14 +21555,12 @@ "wrappy": { "version": "1.0.2", "resolved": false, - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "optional": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "yallist": { "version": "3.0.3", "resolved": false, - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "optional": true + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" } } }, @@ -23154,7 +23113,8 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" } @@ -27904,8 +27864,7 @@ "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "optional": true + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, "binary-extensions": { "version": "1.13.1", @@ -27917,7 +27876,6 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "optional": true, "requires": { "arr-flatten": "^1.1.0", "array-unique": "^0.3.2", @@ -28047,7 +28005,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, "requires": { "is-extendable": "^0.1.0" } @@ -28083,7 +28040,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "optional": true, "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", @@ -28177,8 +28133,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "optional": true + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-glob": { "version": "4.0.1", @@ -28193,7 +28148,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "optional": true, "requires": { "kind-of": "^3.0.2" } diff --git a/public/index.html b/public/index.html index e1058e7d..5954f369 100644 --- a/public/index.html +++ b/public/index.html @@ -7,7 +7,7 @@ content="width=device-width, initial-scale=1, shrink-to-fit=no" /> - Topcoder Challenge Creation App + Work Manager - Topcoder diff --git a/src/actions/challenges.js b/src/actions/challenges.js index 0ddae369..042406c1 100644 --- a/src/actions/challenges.js +++ b/src/actions/challenges.js @@ -10,7 +10,8 @@ import { fetchChallenges, fetchChallengeTerms, fetchResources, - fetchResourceRoles + fetchResourceRoles, + fetchChallengeTimelines } from '../services/challenges' import { LOAD_CHALLENGE_DETAILS_PENDING, @@ -70,8 +71,8 @@ export function loadChallengesByPage (page, projectId, status, filterChallengeNa return fetchChallenges(filters, { page, - perPage: PAGE_SIZE, - memberId: getState().auth.user ? getState().auth.user.userId : null + perPage: PAGE_SIZE + // memberId: getState().auth.user ? getState().auth.user.userId : null }).then((res) => { dispatch({ type: LOAD_CHALLENGES_SUCCESS, @@ -228,6 +229,17 @@ export function loadChallengeTypes () { } } +export function loadChallengeTimelines () { + return async (dispatch) => { + const challengeTimelines = await fetchChallengeTimelines() + dispatch({ + type: LOAD_CHALLENGE_METADATA_SUCCESS, + metadataKey: 'challengeTimelines', + metadataValue: challengeTimelines + }) + } +} + export function loadChallengeTags () { return async (dispatch) => { const challengeTags = await fetchChallengeTags() diff --git a/src/actions/sidebar.js b/src/actions/sidebar.js index 04dd84a8..1abd3c90 100644 --- a/src/actions/sidebar.js +++ b/src/actions/sidebar.js @@ -21,7 +21,7 @@ export function setActiveProject (projectId) { /** * Loads projects of the authenticated user */ -export function loadProjects (filterProjectName = '') { +export function loadProjects (filterProjectName = '', myProjects = true) { return (dispatch) => { dispatch({ type: LOAD_PROJECTS_PENDING @@ -29,11 +29,19 @@ export function loadProjects (filterProjectName = '') { const filters = {} if (!_.isEmpty(filterProjectName)) { - filters['name'] = `*${filterProjectName}*` + if (!isNaN(filterProjectName)) { // if it is number + filters['id'] = parseInt(filterProjectName, 10) + } else { // text search + filters['keyword'] = decodeURIComponent(filterProjectName) + } } filters['status'] = 'active' - filters['sort'] = 'lastActivityAt' - filters['memberOnly'] = 'true' + filters['sort'] = 'lastActivityAt desc' + // filters['perPage'] = 20 + // filters['page'] = 1 + if (myProjects) { + filters['memberOnly'] = true + } fetchMemberProjects(filters).then(projects => dispatch({ type: LOAD_PROJECTS_SUCCESS, diff --git a/src/components/ChallengeEditor/ChallengeEditor.module.scss b/src/components/ChallengeEditor/ChallengeEditor.module.scss index b4d1bc59..79052a69 100644 --- a/src/components/ChallengeEditor/ChallengeEditor.module.scss +++ b/src/components/ChallengeEditor/ChallengeEditor.module.scss @@ -6,6 +6,7 @@ flex-direction: column; margin-bottom: 30px; flex-shrink: 0; + position: relative; } @-moz-document url-prefix() { @@ -236,6 +237,16 @@ padding-bottom: 20px; color: $tc-red } + +.actionButtons { + &.button { + height: 40px; + position: absolute; + top: 30px; + right: 20px; + } +} + .buttonContainer { display: flex; margin: 0px 30px; diff --git a/src/components/ChallengeEditor/ChallengePrizes-Field/index.js b/src/components/ChallengeEditor/ChallengePrizes-Field/index.js index d9149888..ed33c0cd 100644 --- a/src/components/ChallengeEditor/ChallengePrizes-Field/index.js +++ b/src/components/ChallengeEditor/ChallengePrizes-Field/index.js @@ -8,7 +8,7 @@ import PrizeInput from '../../PrizeInput' import styles from './ChallengePrizes-Field.module.scss' import cn from 'classnames' import { PrimaryButton } from '../../Buttons' -import { CHALLENGE_PRIZE_TYPE, VALIDATION_VALUE_TYPE } from '../../../config/constants' +import { CHALLENGE_PRIZE_TYPE, VALIDATION_VALUE_TYPE, PRIZE_SETS_TYPE } from '../../../config/constants' import { validateValue } from '../../../util/input-check' class ChallengePrizesField extends Component { @@ -46,14 +46,14 @@ class ChallengePrizesField extends Component { } onUpdateValue (challengePrize) { - const type = 'Challenge prizes' + const type = PRIZE_SETS_TYPE.CHALLENGE_PRIZES const { onUpdateOthers, challenge } = this.props onUpdateOthers({ field: 'prizeSets', value: [...challenge.prizeSets.filter(p => p.type !== type), challengePrize] }) } getChallengePrize () { - const type = 'Challenge prizes' + const type = PRIZE_SETS_TYPE.CHALLENGE_PRIZES return (this.props.challenge.prizeSets && this.props.challenge.prizeSets.length && this.props.challenge.prizeSets.find(p => p.type === type)) || { type, prizes: [{ type: CHALLENGE_PRIZE_TYPE.MONEY, value: 0 }] } } diff --git a/src/components/ChallengeEditor/ChallengeSchedule-Field/ChallengeSchedule-Field.module.scss b/src/components/ChallengeEditor/ChallengeSchedule-Field/ChallengeSchedule-Field.module.scss index 5884e98a..8afe2400 100644 --- a/src/components/ChallengeEditor/ChallengeSchedule-Field/ChallengeSchedule-Field.module.scss +++ b/src/components/ChallengeEditor/ChallengeSchedule-Field/ChallengeSchedule-Field.module.scss @@ -6,6 +6,10 @@ flex-direction: column; z-index: 3; + .actionButtons { + display: flex; + } + .PhaseRow { display: flex; align-items: center; diff --git a/src/components/ChallengeEditor/ChallengeSchedule-Field/index.js b/src/components/ChallengeEditor/ChallengeSchedule-Field/index.js index 5f87fc58..d0a4e5c8 100644 --- a/src/components/ChallengeEditor/ChallengeSchedule-Field/index.js +++ b/src/components/ChallengeEditor/ChallengeSchedule-Field/index.js @@ -11,7 +11,7 @@ import Chart from 'react-google-charts' import Select from '../../Select' import { parseSVG } from '../../../util/svg' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faAngleDown, faTrash } from '@fortawesome/free-solid-svg-icons' +import { faTrash } from '@fortawesome/free-solid-svg-icons' import PrimaryButton from '../../Buttons/PrimaryButton' const GANTT_ROW_HEIGHT = 45 @@ -24,10 +24,11 @@ class ChallengeScheduleField extends Component { isEdit: false } this.toggleEditMode = this.toggleEditMode.bind(this) - this.renderTimeLine = this.renderTimeLine.bind(this) - this.getChallengePhase = this.getChallengePhase.bind(this) + this.prepareTimeline = this.prepareTimeline.bind(this) + this.getPhaseTemplate = this.getPhaseTemplate.bind(this) + this.getPhaseFromTimelineTemplate = this.getPhaseFromTimelineTemplate.bind(this) + this.recalculatePhaseDates = this.recalculatePhaseDates.bind(this) this.getAllPhases = this.getAllPhases.bind(this) - this.renderTimelineAgain = this.renderTimelineAgain.bind(this) } toggleEditMode () { @@ -35,12 +36,35 @@ class ChallengeScheduleField extends Component { this.setState({ isEdit: !isEdit }) } - getChallengePhase (phase) { + /** + * Finds the phase template from the timeline template. Timeline template contains default duration + * and predecessor information. + * + * @param {Object} phase phase for which template is to be found + */ + getPhaseFromTimelineTemplate (phase) { + const { currentTemplate } = this.props + if (currentTemplate && currentTemplate.phases) { + let templatePhase = currentTemplate.phases.find(tp => tp.phaseId === phase.phaseId) + if (templatePhase) { + templatePhase = _.cloneDeep(templatePhase) + } + return templatePhase + } + return phase + } + + /** + * Finds the phase definition/template from phase templates. Phase templates contains name and description + * + * @param {Object} phase phase for which definition/template is to be found + */ + getPhaseTemplate (phase) { const { challengePhases } = this.props if (!phase) { return phase } - let challengePhase = challengePhases.find(challengePhase => challengePhase.id === phase.phaseId) + let challengePhase = challengePhases.find(cp => cp.id === phase.phaseId) if (challengePhase) { challengePhase = _.cloneDeep(challengePhase) } @@ -49,14 +73,46 @@ class ChallengeScheduleField extends Component { } getAllPhases () { - const { challenge, challengePhasesWithCorrectTimeline } = this.props - if (challengePhasesWithCorrectTimeline && challengePhasesWithCorrectTimeline.length) { - return challengePhasesWithCorrectTimeline - } + const { challenge } = this.props return challenge.phases } - renderTimeLine () { + /** + * Helper method to recalculate the phase dates. It is used just for rendering the timeline. + * Actual population of dates is done in api at + * https://github.com/topcoder-platform/challenge-api/blob/0253c238d67fddadfa2d6c0fb882568b97ce8a20/src/services/ChallengeService.js#L402 + * + * @param {Object} phase phase for which dates are to be calculated + * @param {Array} phases all phases + * @param {Date} startDate start date of the first phase, usually it is challenge's start date + */ + recalculatePhaseDates (phase, phases, startDate) { + const templatePhase = this.getPhaseFromTimelineTemplate(phase) + if (!templatePhase) { + console.warn(`Possible template mismatch. Phase not found in the timeline template of the challenge.`) + } + if (templatePhase && templatePhase.predecessor) { + const prePhase = _.find(phases, (p) => p.phaseId === templatePhase.predecessor) + // `Predecessor ${templatePhase.predecessor} not found from given phases.` + phase.predecessor = prePhase.id + } + if (!phase.predecessor) { + phase.scheduledStartDate = startDate + phase.scheduledEndDate = moment(startDate).add(phase.duration || 0, 'hours').toDate() + phase.actualStartDate = phase.scheduledStartDate + phase.actualEndDate = phase.scheduledEndDate + } else { + const preIndex = _.findIndex(phases, (p) => p.id === phase.predecessor) + // `Invalid phase predecessor: ${phase.predecessor}` + phase.scheduledStartDate = phases[preIndex].scheduledEndDate + phase.scheduledEndDate = moment(phase.scheduledStartDate).add(phase.duration || 0, 'hours').toDate() + phase.actualStartDate = phase.scheduledStartDate + phase.actualEndDate = phase.scheduledEndDate + } + } + + prepareTimeline () { + const { challenge } = this.props const allPhases = this.getAllPhases() if (_.isEmpty(allPhases) || typeof allPhases[0] === 'undefined') { return null @@ -76,8 +132,11 @@ class ChallengeScheduleField extends Component { ) var hourToMilisecond = 60 * 60 * 1000 // = 1 hour + let cStartDate = challenge.startDate _.map(allPhases, (p, index) => { - const phase = this.getChallengePhase(p) + const phase = this.getPhaseTemplate(p) + // recalculate the phase dates, assuming duration is edited by user + this.recalculatePhaseDates(phase, allPhases, cStartDate) if (phase && timelines) { var startDate if (p.scheduledStartDate) { @@ -102,7 +161,7 @@ class ChallengeScheduleField extends Component { } else { percentage = Math.round(((endDate.getTime() - startDate.getTime()) / (hourToMilisecond * p.duration)) * 100) } - const predecessorPhase = phase.predecessor ? this.getChallengePhase(allPhases.filter(ph => ph.phaseId === phase.predecessor)[0]) : null + const predecessorPhase = phase.predecessor ? this.getPhaseTemplate(allPhases.filter(ph => ph.phaseId === phase.predecessor)[0]) : null timelines.push( [ phase.name || '', @@ -127,7 +186,7 @@ class ChallengeScheduleField extends Component { _.map(challenge.phases, (p, index) => (
onUpdatePhase(parseInt(newValue), 'duration', index)} @@ -253,25 +312,12 @@ class ChallengeScheduleField extends Component { textProgressContainer.html(textProgressContainer.html()) } - renderTimelineAgain () { - const { isEdit } = this.state - if (!isEdit) { - this.setState({ isEdit: true }, () => { - this.setState({ isEdit: false }) - }) - } - } - render () { const { isEdit } = this.state - const { currentTemplate, readOnly } = this.props - const { templates, resetPhase, challenge, onUpdateOthers } = this.props - const timelines = !isEdit ? this.renderTimeLine() : null + const { currentTemplate, readOnly, templates } = this.props + const { savePhases, resetPhase, challenge, onUpdateOthers } = this.props + const timelines = !isEdit ? this.prepareTimeline() : null const chartHeight = `${(this.getAllPhases().length * GANTT_ROW_HEIGHT) + GANTT_FOOTER_HEIGHT}px` - if (chartHeight !== this.lastChartHeight) { - this.renderTimelineAgain() - } - this.lastChartHeight = chartHeight return (
@@ -323,12 +369,17 @@ class ChallengeScheduleField extends Component { Timezone: {jstz.determine().name()}
-
-
- Edit - + { !readOnly && + (
+
+ +
-
+ ) + }
{ @@ -361,7 +412,11 @@ class ChallengeScheduleField extends Component { ) } {currentTemplate && isEdit && !readOnly && (
-
+
+ savePhases()} /> {}, resetPhase: () => {}, + savePhases: () => {}, onUpdateSelect: () => {}, onUpdatePhase: () => {}, onUpdateOthers: () => {}, @@ -391,10 +446,10 @@ ChallengeScheduleField.defaultProps = { ChallengeScheduleField.propTypes = { templates: PropTypes.arrayOf(PropTypes.shape()).isRequired, challengePhases: PropTypes.arrayOf(PropTypes.shape()).isRequired, - challengePhasesWithCorrectTimeline: PropTypes.arrayOf(PropTypes.shape()), challenge: PropTypes.shape().isRequired, removePhase: PropTypes.func, resetPhase: PropTypes.func, + savePhases: PropTypes.func, onUpdateSelect: PropTypes.func, onUpdatePhase: PropTypes.func, onUpdateOthers: PropTypes.func.isRequired, diff --git a/src/components/ChallengeEditor/ChallengeView/ChallengeView.module.scss b/src/components/ChallengeEditor/ChallengeView/ChallengeView.module.scss index 8b609d15..e2cac20d 100644 --- a/src/components/ChallengeEditor/ChallengeView/ChallengeView.module.scss +++ b/src/components/ChallengeEditor/ChallengeView/ChallengeView.module.scss @@ -232,6 +232,10 @@ } } +.actionButtons { + display: flex; +} + .button { height: 40px; diff --git a/src/components/ChallengeEditor/ChallengeView/index.js b/src/components/ChallengeEditor/ChallengeView/index.js index 26ede324..a5c7d067 100644 --- a/src/components/ChallengeEditor/ChallengeView/index.js +++ b/src/components/ChallengeEditor/ChallengeView/index.js @@ -50,13 +50,14 @@ const ChallengeView = ({ projectDetail, challenge, metadata, challengeResources, const isInternal = reviewType === 'internal' const timeLineTemplate = _.find(metadata.timelineTemplates, { id: challenge.timelineTemplateId }) if (isLoading || _.isEmpty(metadata.challengePhases) || challenge.id !== challengeId) return - + const showTimeline = false // disables the timeline for time being https://github.com/topcoder-platform/challenge-engine-ui/issues/706 return (
View Details
-
+
+
@@ -122,14 +123,16 @@ const ChallengeView = ({ projectDetail, challenge, metadata, challengeResources, Groups: {challenge.groups ? challenge.groups.join(', ') : ''}
)} - + { showTimeline && ( + + )}
Public specification *
diff --git a/src/components/ChallengeEditor/CheckpointPrizes-Field/index.js b/src/components/ChallengeEditor/CheckpointPrizes-Field/index.js index 86032aeb..9afd7790 100644 --- a/src/components/ChallengeEditor/CheckpointPrizes-Field/index.js +++ b/src/components/ChallengeEditor/CheckpointPrizes-Field/index.js @@ -4,10 +4,10 @@ import styles from './CheckpointPrizes-Field.module.scss' import cn from 'classnames' import { range } from 'lodash' import { validateValue } from '../../../util/input-check' -import { VALIDATION_VALUE_TYPE } from '../../../config/constants' +import { VALIDATION_VALUE_TYPE, PRIZE_SETS_TYPE } from '../../../config/constants' const CheckpointPrizesField = ({ challenge, onUpdateOthers }) => { - const type = 'Checkpoint prizes' + const type = PRIZE_SETS_TYPE.CHECKPOINT_PRIZES const checkpointPrize = challenge.prizeSets.find(p => p.type === type) || { type, prizes: [] } const number = checkpointPrize.prizes.length const amount = checkpointPrize.prizes.length ? checkpointPrize.prizes[0].value : 0 diff --git a/src/components/ChallengeEditor/CopilotFee-Field/index.js b/src/components/ChallengeEditor/CopilotFee-Field/index.js index 734eaf8b..d7d357de 100644 --- a/src/components/ChallengeEditor/CopilotFee-Field/index.js +++ b/src/components/ChallengeEditor/CopilotFee-Field/index.js @@ -5,10 +5,10 @@ import PropTypes from 'prop-types' import { validateValue } from '../../../util/input-check' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faDollarSign } from '@fortawesome/free-solid-svg-icons' -import { VALIDATION_VALUE_TYPE } from '../../../config/constants' +import { VALIDATION_VALUE_TYPE, PRIZE_SETS_TYPE } from '../../../config/constants' const CopilotFeeField = ({ challenge, onUpdateOthers, readOnly }) => { - const type = 'Copilot payment' + const type = PRIZE_SETS_TYPE.COPILOT_PAYMENT const copilotFee = (challenge.prizeSets && challenge.prizeSets.find(p => p.type === type)) || { type, prizes: [{ type, value: 0 }] } const value = copilotFee.prizes[0].value diff --git a/src/components/ChallengeEditor/Description-Field/Description-Field.module.scss b/src/components/ChallengeEditor/Description-Field/Description-Field.module.scss index c888e6f6..f303a246 100644 --- a/src/components/ChallengeEditor/Description-Field/Description-Field.module.scss +++ b/src/components/ChallengeEditor/Description-Field/Description-Field.module.scss @@ -10,6 +10,7 @@ max-height: 410px; display: flex; flex-direction: column; + overflow: auto; &.isPrivate { max-height: 205px; diff --git a/src/components/ChallengeEditor/NDAField/index.js b/src/components/ChallengeEditor/NDAField/index.js index b9cf15a5..ff6b1749 100644 --- a/src/components/ChallengeEditor/NDAField/index.js +++ b/src/components/ChallengeEditor/NDAField/index.js @@ -1,10 +1,11 @@ +import _ from 'lodash' import React from 'react' import PropTypes from 'prop-types' import styles from './NDAField.module.scss' import { DEFAULT_NDA_UUID } from '../../../config/constants' const NDAField = ({ challenge, toggleNdaRequire, readOnly }) => { - const isRequiredNda = challenge.terms && challenge.terms.indexOf(DEFAULT_NDA_UUID) >= 0 + const isRequiredNda = challenge.terms && _.some(challenge.terms, { id: DEFAULT_NDA_UUID }) return (
diff --git a/src/components/ChallengeEditor/ReviewCost-Field/index.js b/src/components/ChallengeEditor/ReviewCost-Field/index.js index d4b9f5a6..11f288fc 100644 --- a/src/components/ChallengeEditor/ReviewCost-Field/index.js +++ b/src/components/ChallengeEditor/ReviewCost-Field/index.js @@ -3,10 +3,10 @@ import PropTypes from 'prop-types' import styles from './ReviewCost-Field.module.scss' import cn from 'classnames' import { validateValue } from '../../../util/input-check' -import { VALIDATION_VALUE_TYPE } from '../../../config/constants' +import { VALIDATION_VALUE_TYPE, PRIZE_SETS_TYPE } from '../../../config/constants' const ReviewCostField = ({ challenge, onUpdateOthers }) => { - const type = 'Reviewer payment' + const type = PRIZE_SETS_TYPE.REVIEWER_PAYMENT const reviewCost = challenge.prizeSets.find(p => p.type === type) || { type, prizes: [{ type, value: 0 }] } const value = reviewCost.prizes[0].value diff --git a/src/components/ChallengeEditor/Type-Field/index.js b/src/components/ChallengeEditor/Type-Field/index.js index f694c569..3dbd2bdc 100644 --- a/src/components/ChallengeEditor/Type-Field/index.js +++ b/src/components/ChallengeEditor/Type-Field/index.js @@ -1,3 +1,4 @@ +import _ from 'lodash' import React from 'react' import PropTypes from 'prop-types' import Select from '../../Select' @@ -14,7 +15,7 @@ const TypeField = ({ types, onUpdateSelect, challenge, disabled }) => {
+ +
+ { + activeProjectId === -1 &&
No project selected. Select one below
+ } + { + isLoading ? : ( +
    + {projectComponents} +
+ ) + } +
+ { activeProjectId !== -1 && + } + ) } } @@ -96,7 +168,9 @@ Challenges.propTypes = { resetSidebarActiveParams: PropTypes.func, page: PropTypes.number.isRequired, perPage: PropTypes.number.isRequired, - totalChallenges: PropTypes.number.isRequired + totalChallenges: PropTypes.number.isRequired, + loadProjects: PropTypes.func.isRequired, + setActiveProject: PropTypes.func.isRequired } const mapStateToProps = ({ challenges, sidebar, projects }) => ({ @@ -111,7 +185,9 @@ const mapStateToProps = ({ challenges, sidebar, projects }) => ({ const mapDispatchToProps = { loadChallengesByPage, resetSidebarActiveParams, - loadProject + loadProject, + loadProjects, + setActiveProject } export default connect(mapStateToProps, mapDispatchToProps)(Challenges) diff --git a/src/containers/Sidebar/index.js b/src/containers/Sidebar/index.js index b8764a94..b58b340f 100644 --- a/src/containers/Sidebar/index.js +++ b/src/containers/Sidebar/index.js @@ -15,15 +15,23 @@ class SidebarContainer extends Component { } componentDidMount () { - this.props.loadProjects() - - const { projectId, activeProjectId } = this.props + const { projectId, activeProjectId, isLoading } = this.props + if (!projectId && activeProjectId === -1 && !isLoading) { + this.props.loadProjects() + } if (projectId && activeProjectId < 0) { this.props.setActiveProject(parseInt(projectId)) } } + componentWillReceiveProps (nextProps) { + const { projectId, isLoading } = nextProps + if (this.props.projectId !== projectId && !projectId && !isLoading) { + this.props.loadProjects() + } + } + updateProjectName (val) { this.setState({ searchProjectName: val }) this.props.loadProjects(val) diff --git a/src/containers/TopbarContainer/index.js b/src/containers/TopbarContainer/index.js index d56bb7a5..3533e475 100644 --- a/src/containers/TopbarContainer/index.js +++ b/src/containers/TopbarContainer/index.js @@ -4,12 +4,19 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import { loadUser } from '../../actions/auth' +import { setActiveProject } from '../../actions/sidebar' import { connect } from 'react-redux' import TopBar from '../../components/TopBar' class TopbarContainer extends Component { componentDidMount () { this.props.loadUser() + + const { projectId, activeProjectId } = this.props + + if (projectId && activeProjectId < 0) { + this.props.setActiveProject(parseInt(projectId)) + } } render () { @@ -20,7 +27,10 @@ class TopbarContainer extends Component { TopbarContainer.propTypes = { loadUser: PropTypes.func.isRequired, - auth: PropTypes.object.isRequired + setActiveProject: PropTypes.func.isRequired, + auth: PropTypes.object.isRequired, + activeProjectId: PropTypes.number, + projectId: PropTypes.string } const mapStateToProps = ({ auth }) => ({ @@ -28,7 +38,8 @@ const mapStateToProps = ({ auth }) => ({ }) const mapDispatchToProps = { - loadUser + loadUser, + setActiveProject } export default connect(mapStateToProps, mapDispatchToProps)(TopbarContainer) diff --git a/src/reducers/challenges.js b/src/reducers/challenges.js index f494756c..5ade02c6 100644 --- a/src/reducers/challenges.js +++ b/src/reducers/challenges.js @@ -23,7 +23,7 @@ import { } from '../config/constants' const initialState = { - isLoading: true, + isLoading: false, challenges: [], metadata: {}, challengeDetails: {}, diff --git a/src/reducers/sidebar.js b/src/reducers/sidebar.js index 5e775474..54e2cf45 100644 --- a/src/reducers/sidebar.js +++ b/src/reducers/sidebar.js @@ -11,14 +11,14 @@ import { const initialState = { activeProjectId: -1, - isLoading: true, + isLoading: false, projects: [] } export default function (state = initialState, action) { switch (action.type) { case SET_ACTIVE_PROJECT: - return { ...state, activeProjectId: action.projectId } + return { ...state, activeProjectId: action.projectId, projects: [], isLoading: false } case LOAD_PROJECTS_SUCCESS: return { ...state, projects: action.projects, isLoading: false, isLoggedIn: true } case LOAD_PROJECTS_PENDING: diff --git a/src/routes.js b/src/routes.js index 72de1741..6e83c648 100644 --- a/src/routes.js +++ b/src/routes.js @@ -82,7 +82,7 @@ class Routes extends React.Component { renderApp( , - , + , )()} /> {/* If path is not defined redirect to landing page */} diff --git a/src/services/challenges.js b/src/services/challenges.js index 9fa6150c..0e29d600 100644 --- a/src/services/challenges.js +++ b/src/services/challenges.js @@ -1,13 +1,14 @@ import _ from 'lodash' import qs from 'qs' import { axiosInstance } from './axiosWithAuth' -import { updateChallengePhaseBeforeSendRequest, convertChallengePhaseFromSecondsToHours } from '../util/date' +import { updateChallengePhaseBeforeSendRequest, convertChallengePhaseFromSecondsToHours, sortChallengePhases } from '../util/date' import FormData from 'form-data' const { CHALLENGE_API_URL, CHALLENGE_TYPES_URL, CHALLENGE_TIMELINE_TEMPLATES_URL, CHALLENGE_PHASES_URL, + CHALLENGE_TIMELINES_URL, GROUPS_API_URL, PLATFORMS_V4_API_URL, TECHNOLOGIES_V4_API_URL, @@ -21,7 +22,7 @@ const { * @returns {Promise<*>} */ export async function fetchChallengeTypes () { - const response = await axiosInstance.get(`${CHALLENGE_TYPES_URL}?isActive=true`) + const response = await axiosInstance.get(`${CHALLENGE_TYPES_URL}`) return _.get(response, 'data', []) } @@ -54,7 +55,16 @@ export async function fetchGroups (filters) { * @returns {Promise<*>} */ export async function fetchTimelineTemplates () { - const response = await axiosInstance.get(CHALLENGE_TIMELINE_TEMPLATES_URL) + const response = await axiosInstance.get(`${CHALLENGE_TIMELINE_TEMPLATES_URL}?page=1&perPage=100`) + return _.get(response, 'data', []) +} + +/** + * Api request for fetching challenge timelines + * @returns {Promise<*>} + */ +export async function fetchChallengeTimelines () { + const response = await axiosInstance.get(`${CHALLENGE_TIMELINES_URL}?page=1&perPage=100`) return _.get(response, 'data', []) } @@ -63,7 +73,7 @@ export async function fetchTimelineTemplates () { * @returns {Promise<*>} */ export async function fetchChallengePhases () { - const response = await axiosInstance.get(CHALLENGE_PHASES_URL) + const response = await axiosInstance.get(`${CHALLENGE_PHASES_URL}?page=1&perPage=100`) convertChallengePhaseFromSecondsToHours(response.data) return _.get(response, 'data', []) } @@ -88,6 +98,7 @@ export async function fetchChallenge (challengeId) { } } convertChallengePhaseFromSecondsToHours(newResponse.phases) + newResponse.phases = sortChallengePhases(newResponse.phases) return newResponse } diff --git a/src/util/date.js b/src/util/date.js index 5dcf3ad0..84618569 100644 --- a/src/util/date.js +++ b/src/util/date.js @@ -56,6 +56,15 @@ export const convertChallengePhaseFromSecondsToHours = (phases) => { } } +/** + * Sorts the challenge phases in order of their supposed execution + * + * @param {Array} phases challenge phases that are to be sorte3d + */ +export const sortChallengePhases = (phases) => { + return _.sortBy(phases, phase => phase.actualStartDate || phase.scheduledStartDate) +} + /** * Convert challenge phase from hours to second and remove unnessesary field * @param {Object} challengeDetail challenge detail