From 6c4b5de4dc050a543a8aba1341e2c2e2bc3669dc Mon Sep 17 00:00:00 2001 From: "Dr. Sergey Pogodin" Date: Tue, 19 Sep 2017 00:59:00 +0200 Subject: [PATCH 1/8] CODE: New Challenge Details page - Challenge Terms Submission #518773 by ouyangki to the challenge http://www.topcoder.com/challenge-details/30059277/?type=develop --- .../components/__snapshots__/Select.jsx.snap | 3 + .../__snapshots__/DateRangePicker.jsx.snap | 3 + .../__snapshots__/index.jsx.snap | 4 + __tests__/shared/reducers/challenge.js | 11 - package-lock.json | 2 +- src/server/server.js | 6 +- src/shared/actions/challenge.js | 2 - src/shared/actions/terms.js | 61 ++++++ .../Specification/SideBar/index.jsx | 14 +- .../Specification/SideBar/styles.scss | 4 + .../Specification/TermsModal/TermDetails.jsx | 72 +++++-- .../Specification/TermsModal/TermDetails.scss | 31 ++- .../Specification/TermsModal/index.jsx | 201 +++++++++++------- .../Specification/TermsModal/styles.scss | 178 ++++++++++++---- .../challenge-detail/Specification/index.jsx | 3 + .../Specification/styles.scss | 1 + .../challenge-listing/SRMCard/index.jsx | 9 + .../examples/LoadingIndicators/style.scss | 9 +- .../communities/wipro/Home/style.scss | 2 +- .../wipro/theme/buttons/default.scss | 2 +- .../containers/challenge-detail/index.jsx | 50 ++++- src/shared/reducers/challenge.js | 3 - src/shared/reducers/terms.js | 130 ++++++++++- .../services/__mocks__/data/terms-auth.json | 8 + src/shared/services/__mocks__/terms.js | 1 + src/shared/services/terms.js | 78 ++++--- 26 files changed, 667 insertions(+), 221 deletions(-) diff --git a/__tests__/shared/components/__snapshots__/Select.jsx.snap b/__tests__/shared/components/__snapshots__/Select.jsx.snap index c3bd4c3925..1172de640a 100644 --- a/__tests__/shared/components/__snapshots__/Select.jsx.snap +++ b/__tests__/shared/components/__snapshots__/Select.jsx.snap @@ -11,6 +11,7 @@ exports[`Matches shallow shapshot 1`] = ` clearRenderer={[Function]} clearValueText="Clear value" clearable={true} + closeOnSelect={true} deleteRemoves={true} delimiter="," disabled={false} @@ -32,6 +33,8 @@ exports[`Matches shallow shapshot 1`] = ` noResultsText="No results found" onBlurResetsInput={true} onCloseResetsInput={true} + onSelectResetsInput={true} + openOnClick={true} optionComponent={[Function]} pageSize={5} placeholder="Select..." diff --git a/__tests__/shared/components/challenge-listing/Filters/__snapshots__/DateRangePicker.jsx.snap b/__tests__/shared/components/challenge-listing/Filters/__snapshots__/DateRangePicker.jsx.snap index 821ea1e666..ee3b21c4bd 100644 --- a/__tests__/shared/components/challenge-listing/Filters/__snapshots__/DateRangePicker.jsx.snap +++ b/__tests__/shared/components/challenge-listing/Filters/__snapshots__/DateRangePicker.jsx.snap @@ -18,6 +18,7 @@ exports[`Matches shallow shapshot 1`] = ` hideKeyboardShortcutsPanel={false} horizontalMargin={0} initialVisibleMonth={null} + inputIconPosition="before" isDayBlocked={[Function]} isDayHighlighted={[Function]} isOutsideRange={[Function]} @@ -33,6 +34,7 @@ exports[`Matches shallow shapshot 1`] = ` onFocusChange={[Function]} onNextMonthClick={[Function]} onPrevMonthClick={[Function]} + openDirection="down" orientation="horizontal" phrases={ Object { @@ -77,6 +79,7 @@ exports[`Matches shallow shapshot 1`] = ` startDate={null} startDateId="startDate" startDatePlaceholderText="Start Date" + weekDayFormat="dd" withFullScreenPortal={false} withPortal={false} /> 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/package-lock.json b/package-lock.json index 0a83b8454a..13a000b5c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12409,7 +12409,7 @@ } }, "tc-accounts": { - "version": "git+https://github.com/appirio-tech/accounts-app.git#6970056342ba9292394e347b61a8b0bc1ba747d1", + "version": "git+https://github.com/appirio-tech/accounts-app.git#36831dfdeeeff3de282b94500db33313e02095a6", "requires": { "@uirouter/angularjs": "1.0.5", "angucomplete-alt": "https://registry.npmjs.org/angucomplete-alt/-/angucomplete-alt-2.5.0.tgz", 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/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 + +
) + } +
+ }
} { @@ -43,10 +75,11 @@ export default class TermDetails extends React.Component { details.agreeabilityType !== 'Electronically-agreeable' && details.docusignTemplateId && !loadingDocuSignUrl && docuSignUrl &&
-