diff --git a/.exchange-rates.cache b/.exchange-rates.cache
index 3ddffed910..3023d7ea24 100644
--- a/.exchange-rates.cache
+++ b/.exchange-rates.cache
@@ -1 +1 @@
-{"disclaimer":"Usage subject to terms: https://openexchangerates.org/terms","license":"https://openexchangerates.org/license","timestamp":1505736000,"base":"USD","rates":{"AED":3.673158,"AFN":68.5815,"ALL":111.8,"AMD":477.790313,"ANG":1.778804,"AOA":165.9215,"ARS":16.9755,"AUD":1.25125,"AWG":1.795504,"AZN":1.7,"BAM":1.634564,"BBD":2,"BDT":80.672708,"BGN":1.636335,"BHD":0.377082,"BIF":1741.930974,"BMD":1,"BND":1.34545,"BOB":6.960491,"BRL":3.109108,"BSD":1,"BTC":0.000253264491,"BTN":64.116668,"BWP":10.145655,"BYN":1.937212,"BZD":2.014389,"CAD":1.220886,"CDF":1562.881563,"CHF":0.959897,"CLF":0.02317,"CLP":624.3,"CNH":6.573772,"CNY":6.567823,"COP":2895.45,"CRC":576.47,"CUC":1,"CUP":25.5,"CVE":92.5,"CZK":21.844736,"DJF":178.57,"DKK":6.230533,"DOP":47.218414,"DZD":111.31,"EGP":17.657,"ERN":15.33,"ETB":23.555095,"EUR":0.837253,"FJD":2.021499,"FKP":0.738416,"GBP":0.738416,"GEL":2.4678,"GGP":0.738416,"GHS":4.389404,"GIP":0.738416,"GMD":45.875,"GNF":8956.4,"GTQ":7.288462,"GYD":207.825,"HKD":7.816553,"HNL":23.374343,"HRK":6.263403,"HTG":62.980903,"HUF":259.437857,"IDR":13250.485412,"ILS":3.522186,"IMP":0.738416,"INR":64.13575,"IQD":1165.334957,"IRR":33322.5,"ISK":106.536883,"JEP":0.738416,"JMD":130.80781,"JOD":0.7085,"JPY":111.36808333,"KES":103.163661,"KGS":68.5084,"KHR":4056.35,"KMF":412.198944,"KPW":900,"KRW":1127.07,"KWD":0.301343,"KYD":0.832734,"KZT":340.315,"LAK":8280.05,"LBP":1506.95,"LKR":153.01,"LRD":117.002232,"LSL":13.175459,"LYD":1.361065,"MAD":9.344244,"MDL":17.643449,"MGA":2988,"MKD":51.51,"MMK":1345.1,"MNT":2459.426676,"MOP":8.045534,"MRO":364.77,"MUR":33.434,"MVR":15.450233,"MWK":725.38,"MXN":17.68575,"MYR":4.186467,"MZN":61.32,"NAD":13.176312,"NGN":359.885566,"NIO":30.345231,"NOK":7.824174,"NPR":102.523085,"NZD":1.372936,"OMR":0.384999,"PAB":1,"PEN":3.250448,"PGK":3.237256,"PHP":51.254,"PKR":105.279978,"PLN":3.5905,"PYG":5655.55,"QAR":3.6391,"RON":3.8506,"RSD":99.645,"RUB":57.747,"RWF":830.004133,"SAR":3.7509,"SBD":7.830881,"SCR":13.550608,"SDG":6.672493,"SEK":7.970225,"SGD":1.346251,"SHP":0.738416,"SLL":7562.5,"SOS":577.104786,"SRD":7.438,"SSP":126.0479,"STD":20503.299805,"SVC":8.743343,"SYP":515.10499,"SZL":13.181602,"THB":33.090672,"TJS":8.79211,"TMT":3.50998,"TND":2.439804,"TOP":2.232925,"TRY":3.4586,"TTD":6.74155,"TWD":30.098212,"TZS":2244.6,"UAH":26.142754,"UGX":3593.6,"USD":1,"UYU":28.997418,"UZS":8086.95,"VEF":9.985022,"VND":22727.933333,"VUV":104.754453,"WST":2.500057,"XAF":549.202101,"XAG":0.05731152,"XAU":0.00076117,"XCD":2.70255,"XDR":0.702343,"XOF":549.202101,"XPD":0.00107202,"XPF":99.910884,"XPT":0.00103467,"YER":250.319142,"ZAR":13.207619,"ZMW":9.628588,"ZWL":322.355011}}
\ No newline at end of file
+{"disclaimer":"Usage subject to terms: https://openexchangerates.org/terms","license":"https://openexchangerates.org/license","timestamp":1505811600,"base":"USD","rates":{"AED":3.673014,"AFN":68.614,"ALL":111.776085,"AMD":478.295,"ANG":1.77968,"AOA":165.9215,"ARS":17.0995,"AUD":1.252216,"AWG":1.796504,"AZN":1.6855,"BAM":1.635351,"BBD":2,"BDT":80.708351,"BGN":1.63299,"BHD":0.377284,"BIF":1745.45,"BMD":1,"BND":1.348334,"BOB":6.963698,"BRL":3.13655,"BSD":1,"BTC":0.000253137519,"BTN":64.166558,"BWP":10.15073,"BYN":1.938191,"BZD":2.015375,"CAD":1.228379,"CDF":1562.881563,"CHF":0.961344,"CLF":0.02325,"CLP":624.6,"CNH":6.585748,"CNY":6.583106,"COP":2909,"CRC":576.745,"CUC":1,"CUP":25.5,"CVE":92.55,"CZK":21.791915,"DJF":178.57,"DKK":6.209958,"DOP":47.241072,"DZD":111.282333,"EGP":17.6487,"ERN":15.331922,"ETB":23.566647,"EUR":0.834449,"FJD":2.012549,"FKP":0.742007,"GBP":0.742007,"GEL":2.4717,"GGP":0.742007,"GHS":4.381672,"GIP":0.742007,"GMD":45.95,"GNF":8960.5,"GTQ":7.292123,"GYD":207.92,"HKD":7.803084,"HNL":23.385811,"HRK":6.235527,"HTG":63.0095,"HUF":258.117002,"IDR":13264.557907,"ILS":3.52281,"IMP":0.742007,"INR":64.245,"IQD":1165.609957,"IRR":33337.5,"ISK":105.94,"JEP":0.742007,"JMD":130.865,"JOD":0.709001,"JPY":111.5666875,"KES":103.367854,"KGS":68.455728,"KHR":4058.3,"KMF":412.561444,"KPW":900,"KRW":1131.54,"KWD":0.301307,"KYD":0.833119,"KZT":340.305898,"LAK":8284.05,"LBP":1507.65,"LKR":153.101598,"LRD":117.111232,"LSL":13.182434,"LYD":1.361748,"MAD":9.333026,"MDL":17.669492,"MGA":2989.5,"MKD":51.401508,"MMK":1345.8,"MNT":2468.97015,"MOP":8.049209,"MRO":364.935,"MUR":33.4205,"MVR":15.450233,"MWK":724.527458,"MXN":17.810955,"MYR":4.191489,"MZN":61.732957,"NAD":13.182434,"NGN":360.37226,"NIO":30.359705,"NOK":7.804146,"NPR":102.548085,"NZD":1.372687,"OMR":0.385015,"PAB":1,"PEN":3.247336,"PGK":3.23879,"PHP":50.99,"PKR":105.333422,"PLN":3.5831,"PYG":5658.3,"QAR":3.656908,"RON":3.840574,"RSD":99.257652,"RUB":58.1634,"RWF":831.55,"SAR":3.75035,"SBD":7.823231,"SCR":13.55,"SDG":6.675602,"SEK":7.95019,"SGD":1.348793,"SHP":0.742007,"SLL":7558.984779,"SOS":578.446731,"SRD":7.438,"SSP":126.2714,"STD":20487.565958,"SVC":8.747714,"SYP":515.00499,"SZL":13.187884,"THB":33.083,"TJS":8.795919,"TMT":3.50998,"TND":2.437494,"TOP":2.2232,"TRY":3.501853,"TTD":6.74491,"TWD":30.156,"TZS":2243.2,"UAH":26.155965,"UGX":3595.3,"USD":1,"UYU":28.986914,"UZS":8090.5,"VEF":9.995002,"VND":22731.335859,"VUV":104.480024,"WST":2.480432,"XAF":547.362622,"XAG":0.05821916,"XAU":0.00076407,"XCD":2.70255,"XDR":0.702671,"XOF":547.362622,"XPD":0.00107407,"XPF":99.576246,"XPT":0.00104276,"YER":250.344142,"ZAR":13.330636,"ZMW":9.633257,"ZWL":322.355011}}
\ No newline at end of file
diff --git a/README.md b/README.md
index 00dd8ac7fb..09689571ce 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ If you need any operations related to currency conversions, pay attention to the
- `PORT` Specifies the port to run the App at. Defaults to 3000;
- `NODE_ENV` Specifies Topcoder backend to use. Should be either `development` either `production`. Defaults to `production`.
-5. To rebuild the App's frontend (initially, it is automatically build as a part of the install step) run one of (the result of build will be output into `/build` folder in both cases):
+5. To build the App's frontend run one of (the result of build will be output into `/build` folder in both cases):
- `$ npm run build` To rebuild production frontend;
- `$ npm run build:dev` This command should only be used to test whether development build of the front end works. You don't have to execute this command to run development version of the App (the server will automatically build frontend in memory anyway). You can't successfully execute this command without installing dev dependencies.
diff --git a/__tests__/shared/components/challenge-listing/__snapshots__/index.jsx.snap b/__tests__/shared/components/challenge-listing/__snapshots__/index.jsx.snap
index 4974af4e1d..2bce595e93 100644
--- a/__tests__/shared/components/challenge-listing/__snapshots__/index.jsx.snap
+++ b/__tests__/shared/components/challenge-listing/__snapshots__/index.jsx.snap
@@ -23,6 +23,7 @@ exports[`Matches shallow shapshot 1 shapshot 1 1`] = `
@@ -40,6 +41,7 @@ exports[`Matches shallow shapshot 1 shapshot 1 1`] = `
@@ -135,6 +137,7 @@ exports[`Matches shallow shapshot 2 shapshot 2 1`] = `
@@ -152,6 +155,7 @@ exports[`Matches shallow shapshot 2 shapshot 2 1`] = `
diff --git a/__tests__/shared/reducers/challenge.js b/__tests__/shared/reducers/challenge.js
index 9c9261f882..c8ac648ec2 100644
--- a/__tests__/shared/reducers/challenge.js
+++ b/__tests__/shared/reducers/challenge.js
@@ -86,7 +86,6 @@ function testReducer(reducer, istate) {
resultsLoadedForChallengeId: '',
selectedTab: 'details',
unregistering: false,
- showTermsModal: false,
});
});
@@ -112,7 +111,6 @@ function testReducer(reducer, istate) {
resultsLoadedForChallengeId: '',
selectedTab: 'details',
unregistering: false,
- showTermsModal: false,
});
});
@@ -132,7 +130,6 @@ function testReducer(reducer, istate) {
resultsLoadedForChallengeId: '',
selectedTab: 'details',
unregistering: false,
- showTermsModal: false,
});
});
@@ -154,7 +151,6 @@ function testReducer(reducer, istate) {
resultsLoadedForChallengeId: '',
selectedTab: 'details',
unregistering: false,
- showTermsModal: false,
});
});
@@ -177,7 +173,6 @@ function testReducer(reducer, istate) {
resultsLoadedForChallengeId: '',
selectedTab: 'details',
unregistering: false,
- showTermsModal: false,
});
});
@@ -200,7 +195,6 @@ function testReducer(reducer, istate) {
resultsLoadedForChallengeId: '',
selectedTab: 'details',
unregistering: false,
- showTermsModal: false,
});
});
@@ -223,7 +217,6 @@ function testReducer(reducer, istate) {
resultsLoadedForChallengeId: '',
selectedTab: 'details',
unregistering: false,
- showTermsModal: false,
});
});
}
@@ -242,7 +235,6 @@ describe('Default reducer', () =>
resultsLoadedForChallengeId: '',
selectedTab: 'details',
unregistering: false,
- showTermsModal: false,
}),
);
@@ -260,7 +252,6 @@ describe('Factory without http request', () =>
mySubmissionsManagement: {},
registering: false,
unregistering: false,
- showTermsModal: false,
}),
),
);
@@ -282,7 +273,6 @@ describe('Factory with server-side rendering', () =>
mySubmissionsManagement: {},
registering: false,
unregistering: false,
- showTermsModal: false,
}),
),
);
@@ -300,7 +290,6 @@ describe('Factory without server-side rendering', () =>
mySubmissionsManagement: {},
registering: false,
unregistering: false,
- showTermsModal: false,
}),
),
);
diff --git a/docs/how-to-add-a-new-topcoder-community.md b/docs/how-to-add-a-new-topcoder-community.md
index a375e37e4d..ee708700fc 100644
--- a/docs/how-to-add-a-new-topcoder-community.md
+++ b/docs/how-to-add-a-new-topcoder-community.md
@@ -123,10 +123,6 @@ To add a new community with the name **demo**, we should follow the following pr
5. At this point **demo** community is ready and accessible at the `/community/demo` route of the App (i.e., if we deploy dev version of the App to `community-west.topcoder-dev.com`, community will be accessible as `community-west.topcoder-dev.com/community/demo`).
- To make **demo** community accessible via a dedicated sub-domain, e.g. like `demo.topcoder-dev.com`, you should edit `/src/shared/routes/index.jsx`. In the first `if-else` block inside `Routes()` function add the line
- ```js
- else if (subdomains.indexOf('demo') >= 0) communityId = 'demo';
- ```
- This takes care about proper sub-domain routing from our App's side. Beside it you should:
+ To make **demo** community accessible via a dedicated sub-domain, e.g. like `demo.topcoder-dev.com`, you should edit `/src/shared/routes/subdomains.js`; add `demo: 'demo',` record (i.e. the format is `subdomain: 'communityId'`) into the `SUBDOMAIN_COMMUNITY` map. Beside it you should:
- Ensure that the web-server where the App is deployed allows access to the subdomain `demo.topcoder-dev`, and redirects incoming requests to the App.
- Ensure that Topcoder `accounts-app` allows to authenticate from the new subdomain address.
diff --git a/package-lock.json b/package-lock.json
index c49e27d2dc..f81a0f5eb5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -4935,6 +4935,11 @@
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz",
"integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA="
},
+ "is_js": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/is_js/-/is_js-0.9.0.tgz",
+ "integrity": "sha1-CrlFQFArp6+iTIVqqYVWFmnpxS0="
+ },
"is-absolute-url": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz",
@@ -5172,11 +5177,6 @@
"resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz",
"integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI="
},
- "is_js": {
- "version": "0.9.0",
- "resolved": "https://registry.npmjs.org/is_js/-/is_js-0.9.0.tgz",
- "integrity": "sha1-CrlFQFArp6+iTIVqqYVWFmnpxS0="
- },
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
@@ -11822,6 +11822,14 @@
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
},
+ "string_decoder": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
+ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
+ "requires": {
+ "safe-buffer": "5.1.1"
+ }
+ },
"string-convert": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
@@ -11851,14 +11859,6 @@
"strip-ansi": "3.0.1"
}
},
- "string_decoder": {
- "version": "1.0.3",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
- "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
- "requires": {
- "safe-buffer": "5.1.1"
- }
- },
"stringstream": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
@@ -12394,10 +12394,6 @@
"resolved": "https://registry.npmjs.org/@uirouter/core/-/core-5.0.5.tgz",
"integrity": "sha512-z7zOXZKEFOloIeSMtsDpudWWfXd7L2qmhyxOAve4ZGFYwBn98zYBd2R4CIlPWMpcm4ZwfhIMTVUxCDgSSXrPKw=="
},
- "Base64": {
- "version": "https://registry.npmjs.org/Base64/-/Base64-0.1.4.tgz",
- "integrity": "sha1-6fbGvvVn/WNepBYqsU3TKedKpt4="
- },
"abbrev": {
"version": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.0.tgz",
"integrity": "sha1-0FVMIlZjbi9W58LlrRg/hZQo2B8="
@@ -13488,6 +13484,10 @@
"version": "https://registry.npmjs.org/base62/-/base62-1.2.0.tgz",
"integrity": "sha1-MeflYNyEbJ9EwaUx32UU2jVHQVc="
},
+ "Base64": {
+ "version": "https://registry.npmjs.org/Base64/-/Base64-0.1.4.tgz",
+ "integrity": "sha1-6fbGvvVn/WNepBYqsU3TKedKpt4="
+ },
"base64-js": {
"version": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz",
"integrity": "sha1-qRlH2h9KUW6jjltOwOw3c2deCIY="
@@ -15456,16 +15456,16 @@
}
}
},
- "string-width": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
- "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M="
- },
"string_decoder": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz",
"integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg="
},
+ "string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M="
+ },
"stringstream": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
@@ -18805,6 +18805,10 @@
"version": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
},
+ "string_decoder": {
+ "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+ "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+ },
"string-width": {
"version": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
@@ -18814,10 +18818,6 @@
"strip-ansi": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz"
}
},
- "string_decoder": {
- "version": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
- "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
- },
"stringstream": {
"version": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz",
"integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg="
diff --git a/src/server/server.js b/src/server/server.js
index 50845422e1..e2fab70e31 100644
--- a/src/server/server.js
+++ b/src/server/server.js
@@ -9,6 +9,7 @@ import path from 'path';
import favicon from 'serve-favicon';
import requestIp from 'request-ip';
import stream from 'stream';
+import serializeJs from 'serialize-javascript';
import { getRates as getExchangeRates } from 'services/money';
import { toJson as xmlToJson } from 'utils/xml2json';
@@ -105,9 +106,10 @@ app.use('/api/exchange-rates', (req, res) => {
getExchangeRates().then(rates => res.send(rates));
});
+/* Receive the signing result from DocuSign server, and then send result to client
+ */
app.use('/iframe-break', (req, res) => {
- const url = req.query.dest;
- res.send(``);
+ res.send(``);
});
/* Serves a mock DocuSign page. Which is, actually, just a simple local
diff --git a/src/shared/actions/challenge.js b/src/shared/actions/challenge.js
index 86042024d1..70ddc89e77 100644
--- a/src/shared/actions/challenge.js
+++ b/src/shared/actions/challenge.js
@@ -171,8 +171,6 @@ export default createActions({
REGISTER_DONE: registerDone,
UNREGISTER_INIT: _.noop,
UNREGISTER_DONE: unregisterDone,
- OPEN_TERMS_MODAL: _.noop,
- CLOSE_TERMS_MODAL: _.noop,
SELECT_TAB: _.identity,
},
});
diff --git a/src/shared/actions/terms.js b/src/shared/actions/terms.js
index e0cd3a826c..8b3b3fc320 100644
--- a/src/shared/actions/terms.js
+++ b/src/shared/actions/terms.js
@@ -54,25 +54,80 @@ function getTermDetailsDone(termId, tokenV2) {
return service.getTermDetails(termId).then(details => ({ termId, details }));
}
+/**
+ * Payload creator for TERMS/GET_DOCU_SIGN_URL_INIT
+ * @param {Number|String} templateId id of document template to sign
+ * @return {String} string format of the id
+ */
function getDocuSignUrlInit(templateId) {
return _.toString(templateId);
}
+/**
+ * Payload creator for TERMS/GET_DOCU_SIGN_URL_DONE
+ * which generate the url of DoduSign term
+ * @param {Number|String} templateId id of document template to sign
+ * @param {String} returnUrl callback url after finishing singing
+ * @param {String} tokenV2 auth token
+ * @return {Promise} promise of request result
+ */
function getDocuSignUrlDone(templateId, returnUrl, tokenV2) {
const service = getService(tokenV2);
return service.getDocuSignUrl(templateId, returnUrl)
.then(resp => ({ templateId, docuSignUrl: resp.recipientViewUrl }));
}
+/**
+ * Payload creator for TERMS/AGREE_TERM_INIT
+ * @param {Number|String} termId id of term
+ * @return {String} string format of the id
+ */
function agreeTermInit(termId) {
return _.toString(termId);
}
+/**
+ * Payload creator for TERMS/AGREE_TERM_DONE
+ * @param {Number|String} termId id of term
+ * @param {String} tokenV2 auth token
+ * @return {Promise} promise of request result
+ */
function agreeTermDone(termId, tokenV2) {
const service = getService(tokenV2);
return service.agreeTerm(termId).then(resp => ({ termId, success: resp.success }));
}
+/**
+ * Payload creator for TERMS/CHECK_STATUS_DONE
+ * which will check if all terms of specified challenge have been agreed,
+ * if not, it will try again after a timeout
+ * @param {Number|String} challengeId id of challenge to check
+ * @param {String} tokenV2 auth token
+ * @return {Promise} promise of request result
+ */
+function checkStatusDone(challengeId, tokenV2) {
+ const TIME_OUT = 10000;
+ const service = getService(tokenV2);
+ const getStatus = (resolve, reject, callback) => {
+ service.getTerms(challengeId).then((res) => {
+ const allAgreed = _.every(res.terms, 'agreed');
+ callback(allAgreed, res.terms);
+ }).catch(err => reject(err));
+ };
+ return new Promise((resolve, reject) => {
+ getStatus(resolve, reject, (allAgreed, terms) => {
+ if (allAgreed) {
+ resolve(terms);
+ } else {
+ // retrive terms again after a timeout, DocuSign result
+ // might take few seconds to get updated
+ setTimeout(() => getStatus(resolve, reject,
+ (a, t) => resolve(t)), TIME_OUT);
+ }
+ });
+ });
+}
+
export default createActions({
TERMS: {
GET_TERMS_INIT: getTermsInit,
@@ -83,5 +138,11 @@ export default createActions({
GET_DOCU_SIGN_URL_DONE: getDocuSignUrlDone,
AGREE_TERM_INIT: agreeTermInit,
AGREE_TERM_DONE: agreeTermDone,
+ OPEN_TERMS_MODAL: _.identity,
+ CLOSE_TERMS_MODAL: _.noop,
+ SELECT_TERM: _.identity,
+ SIGN_DOCU: _.identity,
+ CHECK_STATUS_INIT: _.noop,
+ CHECK_STATUS_DONE: checkStatusDone,
},
});
diff --git a/src/shared/components/challenge-detail/Header/index.jsx b/src/shared/components/challenge-detail/Header/index.jsx
index 87f5c2aedd..56585a5bd8 100755
--- a/src/shared/components/challenge-detail/Header/index.jsx
+++ b/src/shared/components/challenge-detail/Header/index.jsx
@@ -54,7 +54,6 @@ export default function ChallengeHeader(props) {
reliabilityBonus,
userDetails,
currentPhases,
- registrationEndDate,
submissionEndDate,
numRegistrants,
numSubmissions,
@@ -63,6 +62,10 @@ export default function ChallengeHeader(props) {
appealsEndDate,
} = challenge;
+ const registrationPhase = allPhases.find(p => p.phaseType === 'Registration');
+ const registrationEndDate = registrationPhase.actualEndTime
+ || registrationPhase.scheduledEndTime;
+
let trackLower = track ? track.toLowerCase() : 'design';
if (technologies.includes('Data Science')) {
trackLower = 'datasci';
@@ -93,6 +96,10 @@ export default function ChallengeHeader(props) {
if (props.showDeadlineDetail) {
relevantPhases = (allPhases || []).filter((phase) => {
+ if (phase.phaseType === 'Iterative Review') {
+ const end = phase.actualEndTime || phase.scheduledEndTime;
+ return moment(end).isAfter(moment());
+ }
const phaseLowerCase = phase.phaseType.toLowerCase();
if (phaseLowerCase.includes('screening') || phaseLowerCase.includes('specification')) {
return false;
diff --git a/src/shared/components/challenge-detail/Specification/SideBar/index.jsx b/src/shared/components/challenge-detail/Specification/SideBar/index.jsx
index 104c3c4c92..32b90a7cbd 100644
--- a/src/shared/components/challenge-detail/Specification/SideBar/index.jsx
+++ b/src/shared/components/challenge-detail/Specification/SideBar/index.jsx
@@ -1,3 +1,5 @@
+/* eslint jsx-a11y/no-static-element-interactions:0 */
+
import config from 'utils/config';
import React from 'react';
import PT from 'prop-types';
@@ -21,6 +23,7 @@ export default function SideBar({
reviewType,
isDesign,
terms,
+ openTermsModal,
}) {
const scorecardURL = `${config.URL.ONLINE_REVIEW}/review/actions/ViewScorecard?scid=`;
const faqURL = config.URL.INFO.DESIGN_CHALLENGE_SUBMISSION;
@@ -31,9 +34,6 @@ export default function SideBar({
submissionLimitDisplay = `${submissionLimit} submissions`;
}
- const downloadsPlaceHolder = hasRegistered ?
- 'None' : 'Register to Download Files (if available)';
-
const reviewTypeTitle = reviewType === 'PEER' ? 'Peer Review' : 'Community Review Board';
const reviewTypeDescription = (
reviewType === 'PEER' ?
@@ -56,7 +56,7 @@ export default function SideBar({
DOWNLOADS:
{
- hasRegistered && documents && documents.length > 0 ? (
+ hasRegistered && documents && documents.length > 0 && (
{
documents.map(doc => (
@@ -64,8 +64,7 @@ export default function SideBar({
))
}
- ) :
-
{downloadsPlaceHolder}
+ )
}
{eventDetail && (
@@ -182,7 +181,7 @@ export default function SideBar({
terms.map(t => (
@@ -249,4 +248,5 @@ SideBar.propTypes = {
reviewType: PT.string,
isDesign: PT.bool,
terms: PT.arrayOf(PT.shape()),
+ openTermsModal: PT.func.isRequired,
};
diff --git a/src/shared/components/challenge-detail/Specification/SideBar/styles.scss b/src/shared/components/challenge-detail/Specification/SideBar/styles.scss
index 568301c165..df4685ef26 100644
--- a/src/shared/components/challenge-detail/Specification/SideBar/styles.scss
+++ b/src/shared/components/challenge-detail/Specification/SideBar/styles.scss
@@ -135,6 +135,10 @@ $tc-link-visited: #0c4e98;
.term {
display: flex;
align-items: flex-start;
+
+ a {
+ cursor: pointer;
+ }
}
.agreed {
diff --git a/src/shared/components/challenge-detail/Specification/TermsModal/TermDetails.jsx b/src/shared/components/challenge-detail/Specification/TermsModal/TermDetails.jsx
index d6ac86c4f0..54c6047529 100644
--- a/src/shared/components/challenge-detail/Specification/TermsModal/TermDetails.jsx
+++ b/src/shared/components/challenge-detail/Specification/TermsModal/TermDetails.jsx
@@ -2,36 +2,68 @@
import React from 'react';
import PT from 'prop-types';
-import { PrimaryButton } from 'components/buttons';
+import cn from 'classnames';
+import { PrimaryButton, Button } from 'components/buttons';
import LoadingIndicator from 'components/LoadingIndicator';
-import './TermDetails.scss';
+import style from './TermDetails.scss';
export default class TermDetails extends React.Component {
- componentDidMount() {
+ constructor(props) {
+ super(props);
+ this.state = {
+ loadingFrame: false,
+ };
+ this.frameLoaded = this.frameLoaded.bind(this);
+ }
+
+ componentWillMount() {
const { details } = this.props;
if (details.agreeabilityType !== 'Electronically-agreeable' && details.docusignTemplateId) {
this.props.getDocuSignUrl(details.docusignTemplateId);
+ this.setState({ loadingFrame: true });
}
}
+ frameLoaded() {
+ this.setState({
+ loadingFrame: false,
+ });
+ }
+
render() {
- const { details, docuSignUrl, agreeingTerm, agreeTerm,
- deselectTerm, loadingDocuSignUrl } = this.props;
+ const { details, docuSignUrl, agreeingTerm, agreeTerm, closeModal,
+ loadingDocuSignUrl, viewOnly, agreed, nextTerm } = this.props;
+
return (
-
{details.title}
{
details.agreeabilityType === 'Electronically-agreeable' &&
-
-
Back
-
agreeTerm(details.termsOfUseId)}
- >Agree
-
+ {
+ !viewOnly &&
+
+ {
+ agreed ?
+ (
Next ) :
+ (
+
agreeTerm(details.termsOfUseId)}
+ theme={style}
+ >I Agree
+
I Disagree
+
)
+ }
+
+ }
}
{
@@ -43,10 +75,11 @@ export default class TermDetails extends React.Component {
details.agreeabilityType !== 'Electronically-agreeable' && details.docusignTemplateId &&
!loadingDocuSignUrl && docuSignUrl &&
-
-
+ {
+ this.state.loadingFrame &&
+
+ }
+
}
@@ -66,7 +99,10 @@ TermDetails.propTypes = {
docuSignUrl: PT.string,
agreeTerm: PT.func.isRequired,
agreeingTerm: PT.string,
- deselectTerm: PT.func.isRequired,
+ closeModal: PT.func.isRequired,
loadingDocuSignUrl: PT.string,
getDocuSignUrl: PT.func.isRequired,
+ viewOnly: PT.bool.isRequired,
+ agreed: PT.bool.isRequired,
+ nextTerm: PT.func.isRequired,
};
diff --git a/src/shared/components/challenge-detail/Specification/TermsModal/TermDetails.scss b/src/shared/components/challenge-detail/Specification/TermsModal/TermDetails.scss
index 7e095dda3f..28eccc10c2 100644
--- a/src/shared/components/challenge-detail/Specification/TermsModal/TermDetails.scss
+++ b/src/shared/components/challenge-detail/Specification/TermsModal/TermDetails.scss
@@ -1,24 +1,26 @@
@import "~styles/tc-styles";
-.title {
- height: 40px;
- line-height: 40px;
- font-size: 24px;
- border-bottom: 1px solid $tc-gray-10;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
-}
-
.body {
max-height: 480px;
overflow: auto;
- border-bottom: 1px solid $tc-gray-10;
+ border: 1px solid $tc-gray-30;
+ word-break: break-word;
+
+ @include xs {
+ max-height: none;
+ padding: 10px 0;
+ border: none;
+ background-color: $tc-white;
+ }
}
.frame {
height: 480px;
width: 100%;
+
+ &.loading {
+ display: none;
+ }
}
.buttons {
@@ -27,3 +29,10 @@
margin-top: 20px;
padding: 0 50px;
}
+
+.button {
+ height: 30px;
+ padding: 4px 15px;
+ font-size: 13px;
+ line-height: 20px;
+}
diff --git a/src/shared/components/challenge-detail/Specification/TermsModal/index.jsx b/src/shared/components/challenge-detail/Specification/TermsModal/index.jsx
index fdfdc8853d..521642b52b 100644
--- a/src/shared/components/challenge-detail/Specification/TermsModal/index.jsx
+++ b/src/shared/components/challenge-detail/Specification/TermsModal/index.jsx
@@ -1,49 +1,84 @@
/* eslint jsx-a11y/no-static-element-interactions:0 */
+/* global window */
import _ from 'lodash';
import React from 'react';
import PT from 'prop-types';
import cn from 'classnames';
import Modal from 'components/Modal';
-import { PrimaryButton } from 'components/buttons';
+import { PrimaryButton, Button } from 'components/buttons';
import LoadingIndicator from 'components/LoadingIndicator';
import TermDetails from './TermDetails';
-import CheckMark from '../../icons/check-mark.svg';
import styles from './styles.scss';
+
export default class TermsModal extends React.Component {
constructor(props) {
super(props);
- this.state = {
- selectedTerm: null,
- };
-
this.selectTerm = this.selectTerm.bind(this);
+ this.messageHandler = this.messageHandler.bind(this);
+ this.nextTerm = this.nextTerm.bind(this);
+ this.max = 0;
+ }
+
+ componentDidMount() {
+ const { loadDetails, selectedTerm } = this.props;
+ if (selectedTerm) {
+ loadDetails(selectedTerm.termsOfUseId);
+ }
+ window.addEventListener('message', this.messageHandler, false);
}
componentWillReceiveProps(nextProps) {
- if (!_.isEqual(nextProps.agreedTerms, this.props.agreedTerms)) {
- this.setState({
- selectedTerm: null,
- });
- this.props.loadTerms();
+ const { selectedTerm, loadDetails, terms,
+ checkStatus, canRegister, onCancel, register } = this.props;
+ if (nextProps.selectedTerm && !_.isEqual(selectedTerm, nextProps.selectedTerm) &&
+ nextProps.loadingTermId !== _.toString(nextProps.selectedTerm.termsOfUseId)) {
+ loadDetails(nextProps.selectedTerm.termsOfUseId);
+ }
+ if (!_.every(terms, 'agreed') && _.every(nextProps.terms, 'agreed') && !nextProps.checkingStatus) {
+ checkStatus();
}
+ if (!canRegister && nextProps.canRegister) {
+ onCancel();
+ register();
+ }
+ }
+
+ componentWillUnmount() {
+ window.removeEventListener('message', this.messageHandler);
}
selectTerm(term) {
- this.setState({
- selectedTerm: term,
- });
- this.props.loadDetails(term.termsOfUseId);
+ const { selectTerm, selectedTerm } = this.props;
+ if (selectedTerm !== term) {
+ selectTerm(term);
+ }
+ }
+
+ nextTerm() {
+ const { terms, selectTerm } = this.props;
+ const term = _.find(terms, t => !t.agreed);
+ selectTerm(term);
+ }
+
+ messageHandler(event) {
+ const { onCancel, selectedTerm, signDocu } = this.props;
+ if (event.data.type === 'DocuSign') {
+ if (event.data.event === 'signing_complete') {
+ signDocu(selectedTerm.termsOfUseId);
+ } else {
+ onCancel();
+ }
+ }
}
render() {
- const { onCancel, title, terms, details, loadingTermId, docuSignUrl,
- getDocuSignUrl, register, agreeTerm, agreeingTerm,
- isLoadingTerms, registering, loadingDocuSignUrl } = this.props;
- const selectedTerm = this.state.selectedTerm;
+ const { onCancel, terms, details, loadingTermId, docuSignUrl,
+ getDocuSignUrl, agreeTerm, agreeingTerm, isLoadingTerms,
+ loadingDocuSignUrl, selectedTerm, viewOnly, checkingStatus } = this.props;
return (
@@ -56,62 +91,46 @@ export default class TermsModal extends React.Component {
}
{
- !isLoadingTerms && !selectedTerm &&
-
-
{title}
-
- The following groups of terms apply to this challenge.
- You need to agree to all of the terms within the group before you can register.
-
-
-
-
-
- Terms
- Status
-
-
-
- {
- terms.map(t => (
-
-
- {t.title}
- {
- !t.agreed &&
-
- ( this.selectTerm(t)} styleName="view-agree">
- view and agree
- )
-
- }
-
-
-
- {t.agreed ? 'Completed' : 'Required'}
-
-
- ))
- }
-
-
-
+ !isLoadingTerms &&
+
+
{terms.length > 1 ? 'Terms & Conditions of Use' : terms[0].title}
+
You are seeing these Terms & Conditions because you have registered to a challenge and
+ you have to respect the terms below in order to be able to submit.
{
- terms && terms.length > 0 &&
-
-
{ register(); onCancel(); }}
- >
- Register
-
+ checkingStatus &&
+
+ }
+ {
+ !checkingStatus && terms.length > 1 &&
+
+
+
+ {
+ terms.map(t => (
+
+ ))
+ }
+
+
+
+ {
+ terms.map(t => (
+
this.selectTerm(t)} key={t.termsOfUseId}>{t.title}
+ ))
+ }
+
}
}
{
- !isLoadingTerms && selectedTerm &&
-
+ !isLoadingTerms && !checkingStatus && selectedTerm &&
+
+ {
+ terms.length > 1 &&
{selectedTerm.title}
+ }
{
loadingTermId === _.toString(selectedTerm.termsOfUseId) &&
@@ -124,14 +143,42 @@ export default class TermsModal extends React.Component {
getDocuSignUrl={getDocuSignUrl}
agreeTerm={agreeTerm}
agreeingTerm={agreeingTerm}
- deselectTerm={() => this.setState({ selectedTerm: null })}
+ closeModal={onCancel}
loadingDocuSignUrl={loadingDocuSignUrl}
+ viewOnly={viewOnly}
+ agreed={selectedTerm.agreed}
+ nextTerm={this.nextTerm}
/>
}
}
+ {
+ !isLoadingTerms && !checkingStatus && selectedTerm && details && !viewOnly &&
+ loadingTermId !== _.toString(selectedTerm.termsOfUseId) &&
+ details.agreeabilityType === 'Electronically-agreeable' &&
+
+ {
+ selectedTerm.agreed ?
+ (
Next ) :
+ (
+
agreeTerm(details.termsOfUseId)}
+ theme={styles}
+ >I Agree
+
I Disagree
+
)
+ }
+
+ }
);
}
@@ -147,12 +194,12 @@ TermsModal.defaultProps = {
isLoadingTerms: false,
registering: false,
loadingDocuSignUrl: '',
- agreedTerms: {},
+ selectedTerm: null,
+ viewOnly: false,
};
TermsModal.propTypes = {
onCancel: PT.func.isRequired,
- title: PT.string,
terms: PT.arrayOf(PT.shape()),
loadDetails: PT.func.isRequired,
details: PT.shape(),
@@ -162,9 +209,13 @@ TermsModal.propTypes = {
register: PT.func.isRequired,
agreeTerm: PT.func.isRequired,
agreeingTerm: PT.string,
- loadTerms: PT.func.isRequired,
isLoadingTerms: PT.bool,
- registering: PT.bool,
loadingDocuSignUrl: PT.string,
- agreedTerms: PT.shape(),
+ selectedTerm: PT.shape(),
+ checkStatus: PT.func.isRequired,
+ canRegister: PT.bool.isRequired,
+ checkingStatus: PT.bool.isRequired,
+ signDocu: PT.func.isRequired,
+ selectTerm: PT.func.isRequired,
+ viewOnly: PT.bool,
};
diff --git a/src/shared/components/challenge-detail/Specification/TermsModal/styles.scss b/src/shared/components/challenge-detail/Specification/TermsModal/styles.scss
index 0720c1c691..6d8121fcf9 100644
--- a/src/shared/components/challenge-detail/Specification/TermsModal/styles.scss
+++ b/src/shared/components/challenge-detail/Specification/TermsModal/styles.scss
@@ -5,71 +5,140 @@
}
.title {
- height: 40px;
line-height: 40px;
- font-size: 24px;
- border-bottom: 1px solid $tc-gray-10;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
+ font-size: 28px;
+ text-align: center;
+
+ @include xs {
+ text-align: left;
+ }
}
.desc {
- padding: 27px 0 12px;
+ margin-top: 60px;
font-size: 15px;
+ line-height: 25px;
+
+ @include xs {
+ margin-top: 20px;
+ }
}
-.status-img {
- display: inline-block;
- width: 16px;
- height: 16px;
- vertical-align: middle;
- margin-right: 5px;
+.tabs-labels {
+ overflow-x: auto;
+}
+
+.tabs-outer {
+ margin-top: 30px;
+ margin-bottom: 4px;
+ height: 10px;
+ border-radius: 5px;
+}
+
+.tabs-inner {
+ display: flex;
+ border-radius: 5px;
+ overflow: hidden;
+ background-color: $tc-gray-20;
- &.required {
- background-image: url(assets/images/terms-status-required.png);
+ @include xs {
+ width: auto;
+ display: inline-block;
+ white-space: nowrap;
+ height: 10px;
+ font-size: 0;
}
}
-.terms-table {
- width: 100%;
+.tab {
+ flex: 1;
+ height: 10px;
+ position: relative;
- th {
- border-bottom: 1px solid $tc-gray-10;
- height: 42px;
- vertical-align: middle;
- padding: 0 17px;
- background: #f1f1f1;
- color: $tc-orange;
- text-align: left;
- font-size: 15px;
- font-weight: 400;
+ @include xs {
+ width: 135px;
+ display: inline-block;
+ }
+
+ &.agreed,
+ &.active.view-only {
+ background-color: $tc-green-70;
}
- td {
- border-bottom: 1px solid $tc-gray-10;
- height: 32px;
- vertical-align: middle;
- padding: 10px 17px;
- font-size: 14px;
- color: #666;
+ &.agreed,
+ &.active {
+ .indicator {
+ background-color: $tc-green-70;
+ }
}
- tr:nth-child(2n) {
- td {
- background-color: #f7f7f7;
+ .indicator {
+ height: 10px;
+ width: 10px;
+ border-radius: 100%;
+ background-color: $tc-gray-30;
+ }
+}
+
+.labels {
+ margin-bottom: 40px;
+ display: flex;
+
+ .label {
+ flex: 1;
+ cursor: pointer;
+ left: 0;
+ top: 12px;
+ font-size: 13px;
+ overflow: hidden;
+
+ @include xs {
+ flex: 0 0 135px;
}
}
}
-.view-agree {
- color: $tc-orange;
- cursor: pointer;
+.single {
+ padding-top: 25px;
+}
+
+.sub-title {
+ line-height: 30px;
+ font-size: 20px;
+ margin-bottom: 10px;
+
+ @include xs {
+ padding: 0 15px;
+ }
}
.modal-container {
- width: 960px;
- max-height: 660px;
+ width: 1167px;
+ max-height: 965px;
+ padding: 60px 64px 20px 56px;
+
+ @include md {
+ width: 960px;
+ }
+
+ @include sm {
+ width: 720px;
+ padding: 20px;
+ }
+
+ @include xs {
+ width: 300px;
+ padding: 0;
+ max-height: 80%;
+ overflow-y: auto;
+ top: calc(50% - 50px);
+ }
+}
+
+.top-section {
+ @include xs {
+ padding: 15px;
+ }
}
.button-container {
@@ -77,3 +146,28 @@
display: flex;
justify-content: space-around;
}
+
+.buttons {
+ display: flex;
+ justify-content: space-around;
+ margin-top: 20px;
+ padding: 5px;
+ background-color: $tc-white;
+
+ @include xs {
+ position: fixed;
+ bottom: 0;
+ height: 50px;
+ margin-top: 0;
+ left: 0;
+ right: 0;
+ z-index: 999999;
+ }
+}
+
+.button {
+ height: 30px;
+ padding: 4px 15px;
+ font-size: 13px;
+ line-height: 20px;
+}
diff --git a/src/shared/components/challenge-detail/Specification/index.jsx b/src/shared/components/challenge-detail/Specification/index.jsx
index 1bb061919e..7a738b1224 100644
--- a/src/shared/components/challenge-detail/Specification/index.jsx
+++ b/src/shared/components/challenge-detail/Specification/index.jsx
@@ -14,6 +14,7 @@ export default function ChallengeDetailsView(props) {
const {
terms,
hasRegistered,
+ openTermsModal,
challenge: {
introduction,
detailedRequirements,
@@ -272,6 +273,7 @@ export default function ChallengeDetailsView(props) {
fileTypes={fileTypes}
isDesign={track.toLowerCase() === 'design'}
terms={terms}
+ openTermsModal={openTermsModal}
/>
);
@@ -320,4 +322,5 @@ ChallengeDetailsView.propTypes = {
allowStockArt: PT.bool,
finalSubmissionGuidelines: PT.string,
}),
+ openTermsModal: PT.func.isRequired,
};
diff --git a/src/shared/components/challenge-detail/Specification/styles.scss b/src/shared/components/challenge-detail/Specification/styles.scss
index 38985d2a3d..35866c1756 100644
--- a/src/shared/components/challenge-detail/Specification/styles.scss
+++ b/src/shared/components/challenge-detail/Specification/styles.scss
@@ -28,6 +28,7 @@ $tc-link-visited: #0c4e98;
flex-wrap: wrap;
justify-content: center;
align-items: baseline;
+ word-break: break-word;
.challenge-specifications {
flex: 4;
diff --git a/src/shared/components/challenge-listing/SRMCard/index.jsx b/src/shared/components/challenge-listing/SRMCard/index.jsx
index d7d90635de..17a1142809 100755
--- a/src/shared/components/challenge-listing/SRMCard/index.jsx
+++ b/src/shared/components/challenge-listing/SRMCard/index.jsx
@@ -106,6 +106,10 @@ const UpcomingSRMs = ({ srmChallenge }) => (
);
+UpcomingSRMs.defaultProps = {
+ srmChallenge: null,
+};
+
UpcomingSRMs.propTypes = {
srmChallenge: PT.shape().isRequired,
};
@@ -130,6 +134,11 @@ const SRMCard = ({ category, srmChallenge }) => (
);
+SRMCard.defaultProps = {
+ category: null,
+ srmChallenge: null,
+};
+
SRMCard.propTypes = {
category: PT.string.isRequired,
srmChallenge: PT.shape().isRequired,
diff --git a/src/shared/components/examples/LoadingIndicators/style.scss b/src/shared/components/examples/LoadingIndicators/style.scss
index e777b2266b..17f546a73c 100644
--- a/src/shared/components/examples/LoadingIndicators/style.scss
+++ b/src/shared/components/examples/LoadingIndicators/style.scss
@@ -37,14 +37,7 @@
}
.striped-bg {
- background:
- repeating-linear-gradient(
- -45deg,
- $tc-white,
- $tc-white 10px,
- $tc-gray-20 10px,
- $tc-gray-20 20px
- );
+ background: repeating-linear-gradient(-45deg, $tc-white, $tc-white 10px, $tc-gray-20 10px, $tc-gray-20 20px);
margin-top: 24px;
padding: 24px;
text-align: center;
diff --git a/src/shared/components/tc-communities/communities/blockchain/Home/index.jsx b/src/shared/components/tc-communities/communities/blockchain/Home/index.jsx
index 8fca56bcf2..5635e95ce2 100644
--- a/src/shared/components/tc-communities/communities/blockchain/Home/index.jsx
+++ b/src/shared/components/tc-communities/communities/blockchain/Home/index.jsx
@@ -27,7 +27,7 @@ export default function Home(/* props */) {
-
Learn How to Build Next-Generation, Decentralized Applications with Ethereum Blockchain
-
Ethereum is a decentralized platform that runs smart contracts: applications that run exactly as programmed without any possibility of downtime, censorship, fraud or third party interference.
-
Ethereum joins the security of blockchain technology with the expressiveness and objectivity of coded applications. With a distributed ledger on Ethereum, companies and individuals gain real-time transparency into transactions, costs, and security which in turn builds trust — and delivers enterprise-grade results.
+
Learn How to Build DApps on Ethereum
+
Ethereum is a decentralized platform that allows for the deployment of smart contracts: applications that run exactly as programmed without downtime, censorship, fraud, or third-party interference.
+
Ethereum combines the benefits of blockchain technology with the myriad possibilities of coded applications. Leveraging Ethereum, companies and individuals can exchange value and information transparently, securely, and frictionlessly, maximizing efficiency and minimizing the role of middlemen, and creating a firm basis for mutual trust.
Over the coming weeks we will be adding educational resources, fun challenges to help you hone your skills, and competitive challenges to work on real-world customer projects with Ethereum blockchain.
@@ -56,12 +56,12 @@ export default function Home(/* props */) {
diff --git a/src/shared/components/tc-communities/communities/wipro/Home/style.scss b/src/shared/components/tc-communities/communities/wipro/Home/style.scss
index 8fa3329853..1e1ccbce1d 100644
--- a/src/shared/components/tc-communities/communities/wipro/Home/style.scss
+++ b/src/shared/components/tc-communities/communities/wipro/Home/style.scss
@@ -144,7 +144,7 @@
a:active,
a:focus,
a:hover,
- a:visited, {
+ a:visited {
color: #00a2e0 !important;
text-decoration: underline;
}
diff --git a/src/shared/components/tc-communities/communities/wipro/theme/buttons/default.scss b/src/shared/components/tc-communities/communities/wipro/theme/buttons/default.scss
index bcf2192adb..9dfad97dae 100644
--- a/src/shared/components/tc-communities/communities/wipro/theme/buttons/default.scss
+++ b/src/shared/components/tc-communities/communities/wipro/theme/buttons/default.scss
@@ -35,7 +35,7 @@
a,
&,
- &:visited, {
+ &:visited {
color: #00a2e0 !important;
text-decoration: none !important;
}
diff --git a/src/shared/containers/challenge-detail/index.jsx b/src/shared/containers/challenge-detail/index.jsx
index 87693b2d9a..42d6c98c60 100644
--- a/src/shared/containers/challenge-detail/index.jsx
+++ b/src/shared/containers/challenge-detail/index.jsx
@@ -126,6 +126,7 @@ class ChallengeDetailPageContainer extends React.Component {
challengeId,
challengesUrl,
resultsLoadedForChallengeId,
+ openTermsModal,
} = this.props;
const results = resultsLoadedForChallengeId === _.toString(challengeId)
@@ -181,6 +182,7 @@ class ChallengeDetailPageContainer extends React.Component {
detailedRequirements={this.props.challenge.detailedRequirements}
terms={this.props.terms}
hasRegistered={hasRegistered}
+ openTermsModal={openTermsModal}
/>
}
{
@@ -231,6 +233,9 @@ class ChallengeDetailPageContainer extends React.Component {
title={this.props.challenge.name}
isLoadingTerms={this.props.isLoadingTerms}
terms={this.props.terms}
+ selectedTerm={this.props.selectedTerm}
+ viewOnly={this.props.viewOnly}
+ selectTerm={this.props.selectTerm}
loadTerms={() => this.props.loadTerms(this.props.authTokens, this.props.challengeId)}
loadDetails={termId => this.props.loadTermDetails(this.props.authTokens, termId)}
details={this.props.termDetails}
@@ -239,16 +244,19 @@ class ChallengeDetailPageContainer extends React.Component {
getDocuSignUrl={(templateId) => {
const base = window ? window.location.href.match('.*://[^/]*')[0] : '';
return this.props.getDocuSignUrl(this.props.authTokens,
- templateId, `${base}/iframe-break/?dest=${base}`
- + `${location.pathname}?showTerms=true`);
+ templateId, `${base}/iframe-break`);
}}
register={() => this.props.registerForChallenge(this.props.authTokens,
this.props.challengeId)}
agreeingTerm={this.props.agreeingTerm}
agreeTerm={termId => this.props.agreeTerm(this.props.authTokens, termId)}
- agreedTerms={this.props.agreedTerms}
registering={this.props.registering}
loadingDocuSignUrl={this.props.loadingDocuSignUrl}
+ signDocu={this.props.signDocu}
+ checkStatus={() => this.props.checkStatus(this.props.authTokens,
+ this.props.challengeId)}
+ canRegister={this.props.canRegister}
+ checkingStatus={this.props.checkingStatus}
/>
}
@@ -257,7 +265,6 @@ class ChallengeDetailPageContainer extends React.Component {
}
ChallengeDetailPageContainer.defaultProps = {
- agreedTerms: {},
agreeingTerm: '',
challengesUrl: '/challenges',
checkpointResults: null,
@@ -270,13 +277,16 @@ ChallengeDetailPageContainer.defaultProps = {
loadingTermId: '',
results: null,
showTermsModal: false,
+ selectedTerm: null,
terms: [],
termDetails: {},
tokenV3: null,
+ viewOnly: false,
+ canRegister: false,
+ checkingStatus: false,
};
ChallengeDetailPageContainer.propTypes = {
- agreedTerms: PT.shape(),
agreeingTerm: PT.string,
agreeTerm: PT.func.isRequired,
authTokens: PT.shape().isRequired,
@@ -311,12 +321,19 @@ ChallengeDetailPageContainer.propTypes = {
selectedTab: PT.string.isRequired,
setChallengeListingFilter: PT.func.isRequired,
showTermsModal: PT.bool,
+ selectedTerm: PT.shape(),
+ viewOnly: PT.bool,
+ signDocu: PT.func.isRequired,
+ selectTerm: PT.func.isRequired,
termDetails: PT.shape(),
terms: PT.arrayOf(PT.shape()),
toggleCheckpointFeedback: PT.func.isRequired,
tokenV3: PT.string,
unregisterFromChallenge: PT.func.isRequired,
unregistering: PT.bool.isRequired,
+ checkStatus: PT.func.isRequired,
+ canRegister: PT.bool,
+ checkingStatus: PT.bool,
};
/* TODO: This function is ugly. We should do all this logic within normalization
@@ -401,7 +418,6 @@ function extractChallengeDetail(v3, v2, challengeId) {
}
const mapStateToProps = (state, props) => ({
- agreedTerms: state.terms.agreedTerms,
agreeingTerm: state.terms.agreeingTerm,
authTokens: state.auth,
challenge: extractChallengeDetail(state.challenge.details,
@@ -424,9 +440,13 @@ const mapStateToProps = (state, props) => ({
results: state.challenge.results,
resultsLoadedForChallengeId: state.challenge.resultsLoadedForChallengeId,
selectedTab: state.challenge.selectedTab || 'details',
- showTermsModal: state.challenge.showTermsModal,
+ showTermsModal: state.terms.showTermsModal,
+ selectedTerm: state.terms.selectedTerm,
+ viewOnly: state.terms.viewOnly,
termDetails: state.terms.details,
terms: state.terms.terms,
+ canRegister: state.terms.canRegister,
+ checkingStatus: state.terms.checkingStatus,
tokenV2: state.auth && state.auth.tokenV2,
tokenV3: state.auth && state.auth.tokenV3,
unregistering: state.challenge.unregistering,
@@ -483,11 +503,14 @@ const mapDispatchToProps = (dispatch) => {
dispatch(t.getTermsInit(challengeId));
dispatch(t.getTermsDone(challengeId, tokens.tokenV2));
},
- openTermsModal: () => {
- dispatch(a.openTermsModal());
+ openTermsModal: (term) => {
+ dispatch(t.openTermsModal(term));
},
closeTermsModal: () => {
- dispatch(a.closeTermsModal());
+ dispatch(t.closeTermsModal());
+ },
+ selectTerm: (term) => {
+ dispatch(t.selectTerm(term));
},
loadTermDetails: (tokens, termId) => {
dispatch(t.getTermDetailsInit(termId));
@@ -501,6 +524,13 @@ const mapDispatchToProps = (dispatch) => {
dispatch(t.agreeTermInit(termId));
dispatch(t.agreeTermDone(termId, tokens.tokenV2));
},
+ signDocu: (id) => {
+ dispatch(t.signDocu(id));
+ },
+ checkStatus: (tokens, challengeId) => {
+ dispatch(t.checkStatusInit());
+ dispatch(t.checkStatusDone(challengeId, tokens.tokenV2));
+ },
onSelectorClicked: (tab) => {
dispatch(a.selectTab(tab));
},
diff --git a/src/shared/containers/challenge-detail/styles.scss b/src/shared/containers/challenge-detail/styles.scss
index 7976f73fe1..c14555e932 100644
--- a/src/shared/containers/challenge-detail/styles.scss
+++ b/src/shared/containers/challenge-detail/styles.scss
@@ -2,12 +2,7 @@
.outer-container {
background: $tc-gray-neutral-dark;
- bottom: 0;
- left: 0;
- overflow-y: scroll;
- position: absolute;
- right: 0;
- top: 0;
+ width: 100%;
@include xxs-to-xs {
padding: 8px 10px 20px;
diff --git a/src/shared/containers/challenge-listing/Listing/index.jsx b/src/shared/containers/challenge-listing/Listing/index.jsx
index 0800947fe8..4762bb5069 100644
--- a/src/shared/containers/challenge-listing/Listing/index.jsx
+++ b/src/shared/containers/challenge-listing/Listing/index.jsx
@@ -148,7 +148,7 @@ export class ListingContainer extends React.Component {
if (communityFilter) communityFilter = communityFilter.challengeFilter;
return (
-
+
{/* For demo we hardcode banner properties so we can disable max-len linting */}
{/* eslint-disable max-len */}
{ !listingOnly ? (
diff --git a/src/shared/containers/challenge-listing/Listing/styles.scss b/src/shared/containers/challenge-listing/Listing/styles.scss
index 296c0f66aa..081239b20e 100644
--- a/src/shared/containers/challenge-listing/Listing/styles.scss
+++ b/src/shared/containers/challenge-listing/Listing/styles.scss
@@ -4,9 +4,8 @@ $sm-space-15: $base-unit * 3;
$sm-space-25: $base-unit * 5;
$sm-space-40: $base-unit * 8;
-.outer-container {
- background: $tc-gray-10;
- padding: 24px;
+.container {
+ width: 100%;
}
.submission-management-container {
diff --git a/src/shared/reducers/challenge-listing/index.js b/src/shared/reducers/challenge-listing/index.js
index 645ce1b02a..201beaeab2 100644
--- a/src/shared/reducers/challenge-listing/index.js
+++ b/src/shared/reducers/challenge-listing/index.js
@@ -167,16 +167,24 @@ function onSelectCommunity(state, { payload }) {
* @return {Object}
*/
function onSetFilter(state, { payload }) {
+ /* Validation of filter parameters: they may come from URL query, thus
+ * validation is not a bad idea. As you may note, at the moment we do not
+ * do it very carefuly (many params are not validated). */
const filter = _.clone(payload);
-
- if (filter) {
- if (filter.tags && !_.isArray(filter.tags)) {
- filter.tags = _.values(filter.tags);
- }
- if (filter.subtracks && !_.isArray(filter.subtracks)) {
- filter.subtracks = _.values(filter.subtracks);
- }
+ if (_.isPlainObject(filter.tags)) {
+ filter.tags = _.values(filter.tags);
+ }
+ if (_.isPlainObject(filter.subtracks)) {
+ filter.subtracks = _.values(filter.subtracks);
+ }
+ if (filter.startDate && !moment(filter.startDate).isValid()) {
+ delete filter.startDate;
+ }
+ if (filter.endDate && !moment(filter.endDate).isValid()) {
+ delete filter.endDate;
}
+
+ /* Update of URL and generation of the state. */
updateQuery({ filter });
return {
...state,
@@ -279,41 +287,22 @@ function create(initialState) {
* @return {Promise} Resolves to the new reducer.
*/
export function factory(req) {
- const state = {};
-
- if (req) {
- let filter = {};
- if (req.query && req.query.filter) {
- filter = _.clone(req.query.filter);
- if (filter.tags && !_.isArray(filter.tags)) {
- filter.tags = _.values(filter.tags);
- }
- if (filter.subtracks && !_.isArray(filter.subtracks)) {
- filter.subtracks = _.values(filter.subtracks);
- }
- }
- state.filter = filter;
-
- /* TODO: OK, fine, this validation of dates does the server-side part of
- * the trick, while the frontend part (removing them from URL) is done
- * elsewhere (/src/shared/routes/Topcoder/ChallengeListing), but it should
- * be changed that everything is handled here in the reducer code (only
- * this way we can ensure that it works all around, including community
- * challenge listings). */
- if (!!state.filter && !!state.filter.startDate
- && moment(state.filter.startDate).isValid() === false) {
- delete state.filter.startDate;
- }
- if (!!state.filter && !!state.filter.endDate
- && moment(state.filter.endDate).isValid() === false) {
- delete state.filter.endDate;
+ if (req && req.url.match(/challenges\/?$/)) {
+ let state = {};
+
+ if (req.query.filter) {
+ state = onSetFilter(state, { payload: req.query.filter });
}
state.selectedCommunityId = req.query.communityId;
+
+ return resolveReducers({
+ sidebar: sidebarFactory(req),
+ }).then(reducers => combine(create(state), { ...reducers, filterPanel }));
}
return resolveReducers({
sidebar: sidebarFactory(req),
- }).then(reducers => combine(create(state), { ...reducers, filterPanel }));
+ }).then(reducers => combine(create(), { ...reducers, filterPanel }));
}
/* Default reducer with empty initial state. */
diff --git a/src/shared/reducers/challenge.js b/src/shared/reducers/challenge.js
index cfa3181785..b709a0ca65 100644
--- a/src/shared/reducers/challenge.js
+++ b/src/shared/reducers/challenge.js
@@ -257,8 +257,6 @@ function create(initialState) {
}),
[a.fetchCheckpointsDone]: onFetchCheckpointsDone,
[a.toggleCheckpointFeedback]: onToggleCheckpointFeedback,
- [a.openTermsModal]: state => ({ ...state, showTermsModal: true }),
- [a.closeTermsModal]: state => ({ ...state, showTermsModal: false }),
[a.selectTab]: onSelectTab,
}, _.defaults(initialState, {
details: null,
@@ -271,7 +269,6 @@ function create(initialState) {
results: null,
resultsLoadedForChallengeId: '',
unregistering: false,
- showTermsModal: false,
selectedTab: DETAIL_TABS.DETAILS,
}));
}
diff --git a/src/shared/reducers/tc-communities/meta.js b/src/shared/reducers/tc-communities/meta.js
index f466ee8a78..c520af2982 100644
--- a/src/shared/reducers/tc-communities/meta.js
+++ b/src/shared/reducers/tc-communities/meta.js
@@ -6,6 +6,7 @@ import _ from 'lodash';
import actions from 'actions/tc-communities/meta';
import logger from 'utils/logger';
import { handleActions } from 'redux-actions';
+import { getCommunityId } from 'routes/subdomains';
import { toFSA } from 'utils/redux';
/**
@@ -63,23 +64,16 @@ function create(initialState) {
* @return Promise which resolves to the new reducer.
*/
export function factory(req) {
- const subdomains = (req && req.subdomains) || [];
- if (subdomains.indexOf('wipro') >= 0) {
- const state = { loadingMetaDataForCommunityId: 'wipro' };
- return toFSA(actions.tcCommunities.meta.fetchDataDone('wipro'))
- .then(res => create(onDone(state, res)));
- }
- if (subdomains.indexOf('blockchain') >= 0) {
- const state = { loadingMetaDataForCommunityId: 'blockchain' };
- return toFSA(actions.tcCommunities.meta.fetchDataDone('blockchain'))
- .then(res => create(onDone(state, res)));
- }
-
- if (req && req.url.startsWith('/community')) {
- const communityId = req.url.split('/')[2];
- const state = { loadingMetaDataForCommunityId: communityId };
- return toFSA(actions.tcCommunities.meta.fetchDataDone(communityId))
- .then(res => create(onDone(state, res)));
+ if (req) {
+ let communityId = getCommunityId(req.subdomains);
+ if (!communityId && req.url.startsWith('/community')) {
+ communityId = req.url.split('/')[2];
+ }
+ if (communityId) {
+ const state = { loadingMetaDataForCommunityId: communityId };
+ return toFSA(actions.tcCommunities.meta.fetchDataDone(communityId))
+ .then(res => create(onDone(state, res)));
+ }
}
return Promise.resolve(create());
}
diff --git a/src/shared/reducers/terms.js b/src/shared/reducers/terms.js
index 5d86bb495b..7dc1ddb897 100644
--- a/src/shared/reducers/terms.js
+++ b/src/shared/reducers/terms.js
@@ -40,6 +40,15 @@ function onGetTermsDone(state, action) {
};
}
+/**
+ * Handles TERMS/GET_TERM_DETAILS_DONE action.
+ * Note, that it silently discards received details if the termId of received
+ * mismatches the one stored in loadingDetailsForTermId field
+ * of the state.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
function onGetTermDetailsDone(state, action) {
if (action.error) {
logger.error('Failed to get term details!', action.payload);
@@ -63,6 +72,15 @@ function onGetTermDetailsDone(state, action) {
};
}
+/**
+ * Handles TERMS/GET_DOCU_SIGN_URL_DONE action.
+ * Note, that it silently discards received url if the templateId of received
+ * mismatches the one stored in loadingDocuSignUrl field
+ * of the state.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
function onGetDocuSignUrlDone(state, action) {
if (action.error) {
logger.error('Failed to get docu sign url!', action.payload);
@@ -85,6 +103,15 @@ function onGetDocuSignUrlDone(state, action) {
};
}
+/**
+ * Handles TERMS/AGREE_TERM_DONE action.
+ * Note, that it silently discards received result if the termId of received
+ * mismatches the one stored in agreeingTerm field
+ * of the state.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
function onAgreeTermDone(state, action) {
if (action.error) {
logger.error('Failed to agree term!', action.payload);
@@ -98,17 +125,96 @@ function onAgreeTermDone(state, action) {
if (_.toString(action.payload.termId) !== state.agreeingTerm) {
return state;
}
+ if (action.payload.success) {
+ const terms = _.cloneDeep(state.terms);
+ const term = _.find(terms, ['termsOfUseId', action.payload.termId]);
+ term.agreed = true;
+ const selectedTerm = _.find(terms, t => !t.agreed);
+ return {
+ ...state,
+ terms,
+ selectedTerm,
+ agreeTermFailure: false,
+ agreeingTerm: '',
+ };
+ }
return {
...state,
- agreedTerms: {
- ...state.agreedTerms,
- [action.payload.termId]: action.payload.success,
- },
agreeTermFailure: false,
agreeingTerm: '',
};
}
+/**
+ * Handles TERMS/OPEN_TERMS_MODAL action.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
+function onOpenTermsModal(state, action) {
+ if (action.payload) {
+ return {
+ ...state,
+ showTermsModal: true,
+ selectedTerm: action.payload,
+ viewOnly: true,
+ };
+ }
+ const selectedTerm = _.find(state.terms, t => !t.agreed);
+ return {
+ ...state,
+ showTermsModal: true,
+ selectedTerm,
+ viewOnly: false,
+ };
+}
+
+/**
+ * Handles TERMS/SIGN_DOCU action.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
+function onSignDocu(state, action) {
+ const terms = _.cloneDeep(state.terms);
+ const term = _.find(terms, ['termsOfUseId', action.payload]);
+ term.agreed = true;
+ const selectedTerm = _.find(terms, t => !t.agreed);
+ return {
+ ...state,
+ terms,
+ selectedTerm,
+ };
+}
+
+/**
+ * Handles TERMS/CHECK_STATUS_DONE action.
+ * @param {Object} state
+ * @param {Object} action
+ * @return {Object} New state.
+ */
+function onCheckStatusDone(state, action) {
+ if (action.error) {
+ logger.error('Check terms status failed!', action.payload);
+ return {
+ ...state,
+ checkingStatus: false,
+ checkStatusError: action.payload,
+ canRegister: false,
+ };
+ }
+ const canRegister = _.every(action.payload, 'agreed');
+ const selectedTerm = _.find(action.payload, t => !t.agreed);
+ return {
+ ...state,
+ checkingStatus: false,
+ checkStatusError: false,
+ canRegister,
+ terms: action.payload,
+ selectedTerm,
+ };
+}
+
/**
* Creates a new Terms reducer with the specified initial state.
* @param {Object} initialState Initial state.
@@ -146,10 +252,24 @@ function create(initialState) {
agreeingTerm: payload,
}),
[actions.terms.agreeTermDone]: onAgreeTermDone,
+ [actions.terms.openTermsModal]: onOpenTermsModal,
+ [actions.terms.closeTermsModal]: state => ({ ...state, showTermsModal: false }),
+ [actions.terms.selectTerm]: (state, { payload }) => ({ ...state, selectedTerm: payload }),
+ [actions.terms.signDocu]: onSignDocu,
+ [actions.terms.checkStatusInit]: state => ({
+ ...state,
+ checkingStatus: true,
+ }),
+ [actions.terms.checkStatusDone]: onCheckStatusDone,
}, initialState || {
getTermsFailure: false,
terms: [],
- agreedTerms: {},
+ showTermsModal: false,
+ selectedTerm: null,
+ viewOnly: false,
+ checkingStatus: false,
+ checkStatusError: false,
+ canRegister: false,
});
}
diff --git a/src/shared/routes/Topcoder/ChallengeListing.jsx b/src/shared/routes/Topcoder/ChallengeListing.jsx
index 2c28d0c664..82a93a4698 100644
--- a/src/shared/routes/Topcoder/ChallengeListing.jsx
+++ b/src/shared/routes/Topcoder/ChallengeListing.jsx
@@ -5,11 +5,11 @@
import _ from 'lodash';
import LoadingIndicator from 'components/LoadingIndicator';
+// import path from 'path';
import qs from 'qs';
import React from 'react';
-import moment from 'moment';
-import { SplitRoute } from 'utils/router';
-import { updateQuery } from 'utils/url';
+// import { StaticRouter } from 'react-router-dom';
+import { /* requireWeak, resolveWeak, */ SplitRoute } from 'utils/router';
export default function ChallengeListingRoute() {
return (
@@ -21,24 +21,10 @@ export default function ChallengeListingRoute() {
/* webpackChunkName: "challenge-listing" */
'containers/challenge-listing/Listing',
).then(({ default: ChallengeListing }) => {
+ /* TODO: Choice of currency and prize mode should be moved to
+ * Redux actions / reducers. */
const query = renderProps.location.search ?
qs.parse(renderProps.location.search.slice(1)) : null;
-
- /* TODO: This validation of start and end dates from query params
- * does the trick of removing invalid dates from URL at the client
- * side, but it actually should be done in the reducer as well. */
- if (query) {
- if (query.filter && query.filter.startDate
- && !moment(query.filter.startDate).isValid()) {
- delete query.filter.startDate;
- }
- if (query.filter && query.filter.endDate
- && !moment(query.filter.endDate).isValid()) {
- delete query.filter.endDate;
- }
- updateQuery({ filter: query.filter });
- }
-
const currencyFromUrl = _.get(query, 'currency');
const prizeMode = currencyFromUrl && `money-${currencyFromUrl}`;
return (
@@ -51,6 +37,32 @@ export default function ChallengeListingRoute() {
})
}
renderPlaceholder={() => }
+ /*
+ TODO: Making server-side rendering work demands some more efforts
+ with reducers coding.
+ renderServer={(renderProps) => {
+ const p = resolveWeak('containers/challenge-listing/Listing');
+ const ChallengeListing = requireWeak(path.resolve(__dirname, p));
+ /* TODO: Choice of currency and prize mode should be moved to
+ * Redux actions / reducers. *//*
+ const query = renderProps.location.search ?
+ qs.parse(renderProps.location.search.slice(1)) : null;
+ const currencyFromUrl = _.get(query, 'currency');
+ const prizeMode = currencyFromUrl && `money-${currencyFromUrl}`;
+ return (
+
+
+
+ );
+ }}
+ */
/>
);
}
diff --git a/src/shared/routes/Topcoder/styles.scss b/src/shared/routes/Topcoder/styles.scss
index bd026a4c0e..f4e264276e 100644
--- a/src/shared/routes/Topcoder/styles.scss
+++ b/src/shared/routes/Topcoder/styles.scss
@@ -5,7 +5,7 @@
justify-content: space-between;
> *:nth-child(2):not(:last-child) {
- display: block;
+ display: flex;
flex: 1;
position: relative;
}
diff --git a/src/shared/routes/index.jsx b/src/shared/routes/index.jsx
index f07f647ca1..1337ab2861 100644
--- a/src/shared/routes/index.jsx
+++ b/src/shared/routes/index.jsx
@@ -15,19 +15,10 @@ import { connect } from 'react-redux';
import Communities from './Communities';
import Examples from './Examples';
import Topcoder from './Topcoder';
+import { getCommunityId } from './subdomains';
function Routes({ subdomains }) {
- let communityId;
- if (subdomains.includes('blockchain')) communityId = 'blockchain';
- else if (subdomains.includes('community-2')) communityId = 'community-2';
- else if (subdomains.includes('demo-expert')) communityId = 'demo-expert';
- else if (subdomains.includes('wipro')
- || subdomains.includes('topgear')) communityId = 'wipro';
- else if (subdomains.includes('taskforce')) communityId = 'taskforce';
- else if (subdomains.includes('qa')) communityId = 'qa';
- else if (subdomains.includes('srmx')) communityId = 'srmx';
- else if (subdomains.includes('tc-prod-dev')) communityId = 'tc-prod-dev';
- else if (subdomains.includes('veterans')) communityId = 'veterans';
+ const communityId = getCommunityId(subdomains);
if (communityId) {
return (
res.json())
- .then((res) => {
- if (res.error) {
- if (res.error.details === 'You are already registered for this challenge.') {
- registered = true;
+ if (this.private.tokenV2) {
+ let registered = false;
+ return this.private.api.get(`/terms/${challengeId}?role=Submitter`)
+ .then(res => res.json())
+ .then((res) => {
+ if (res.error) {
+ if (res.error.details === 'You are already registered for this challenge.') {
+ registered = true;
+ }
+ return this.private.api.get(`/terms/${challengeId}?role=Submitter&noauth=true`)
+ .then((resp) => {
+ if (resp.ok) {
+ return resp.json().then((result) => {
+ if (registered) {
+ // eslint-disable-next-line no-param-reassign
+ _.forEach(result.terms, (t) => { t.agreed = true; });
+ }
+ return result;
+ });
+ }
+ return new Error(resp.statusText);
+ });
}
- return this.private.api.get(`/terms/${challengeId}?role=Submitter&noauth=true`)
- .then((resp) => {
- if (resp.ok) {
- return resp.json().then((result) => {
- if (registered) {
- // eslint-disable-next-line no-param-reassign
- _.forEach(result.terms, (t) => { t.agreed = true; });
- }
- return result;
- });
- }
- return new Error(resp.statusText);
- });
+ return res;
+ });
+ }
+ return this.private.api.get(`/terms/${challengeId}?role=Submitter&noauth=true`)
+ .then((resp) => {
+ if (resp.ok) {
+ return resp.json();
}
- return res;
+ return new Error(resp.statusText);
});
}
+ /**
+ * get details of specified term
+ * @param {Number|String} termId id of the term
+ * @return {Promise} promise of the request result
+ */
getTermDetails(termId) {
return this.private.api.get(`/terms/detail/${termId}`)
.then(res => (res.ok ? res.json() : new Error(res.statusText)));
}
+ /**
+ * generate the url of DocuSign term
+ * @param {Number|String} templateId id of the term's template
+ * @param {String} returnUrl callback url after finishing signing
+ * @return {Promise} promise of the request result
+ */
getDocuSignUrl(templateId, returnUrl) {
return this.private.api.post(`/terms/docusign/viewURL?templateId=${templateId}&returnUrl=${returnUrl}`)
.then(res => (res.ok ? res.json() : new Error(res.statusText)));
}
+ /**
+ * Agree a term
+ * @param {Number|String} termId id of the term
+ * @return {Promise} promise of the request result
+ */
agreeTerm(termId) {
return this.private.api.post(`/terms/${termId}/agree`)
.then(res => (res.ok ? res.json() : new Error(res.statusText)));
@@ -62,9 +92,9 @@ class TermsService {
}
/**
- * Returns a new or existing challenges service.
- * @param {String} tokenV3 Optional. Auth token for Topcoder API v3.
- * @return {Challenges} Challenges service object
+ * Returns a new or existing terms service.
+ * @param {String} tokenV2 Optional. Auth token for Topcoder API v2.
+ * @return {TermsService} Terms service object
*/
let lastInstance = null;
export function getService(tokenV2) {