+ const billingAccountsComponent = billingAccounts.length
+ ? (
+ {billingAccountsComponent}
@@ -78,17 +182,17 @@ Title
value={paymentTitle}
/>
-
+
Description
-
+
+
+Submission Guidelines
+
+ {guidelines}
@@ -132,11 +236,55 @@ $
/>
+
+
+ Copilot
+
+ {
+ setCopilotInputSelected(member);
+ setCopilot(member.handle);
+ }}
+ onKeywordChange={(keyword) => {
+ setCopilotInputKeyword(keyword);
+ getCopilotSuggestions(keyword);
+ }}
+ member={challengeCopilot}
+ />
+
+
+
+Copilot Amount
+
+
+
+
setCopilotPaymentAmount(Number(e.target.value))}
+ placeholder="0"
+ type="number"
+ value={String(copilotPaymentAmount)}
+ />
+
+
+ {techTags}
+ {updatedAt}
- { paymentAmount
- && neu
- && paymentAssignee
+ { neu
+ && ((paymentAmount && paymentAssignee) || (copilot && copilotPaymentAmount))
&& paymentDescription
+ // && challengeTechnologyTags.length
&& paymentTitle ? (
- ) : (
-
- This project has no associated billing accounts yet
-
);
}
@@ -207,9 +351,20 @@ Editor.propTypes = {
setMemberInputKeyword: PT.func.isRequired,
memberInputSelected: PT.shape().isRequired,
setMemberInputSelected: PT.func.isRequired,
+ copilotSuggestions: PT.arrayOf(PT.shape()).isRequired,
+ getCopilotSuggestions: PT.func.isRequired,
+ copilotInputPopupVisible: PT.bool.isRequired,
+ setCopilotInputPopupVisible: PT.func.isRequired,
+ copilotInputKeyword: PT.string.isRequired,
+ setCopilotInputKeyword: PT.func.isRequired,
+ copilotInputSelected: PT.shape().isRequired,
+ setCopilotInputSelected: PT.func.isRequired,
+ copilotPaymentAmount: PT.number.isRequired,
+ copilot: PT.string.isRequired,
paymentAmount: PT.number.isRequired,
paymentAssignee: PT.string.isRequired,
paymentDescription: PT.string.isRequired,
+ submissionGuidelines: PT.string.isRequired,
paymentTitle: PT.string.isRequired,
projectDetails: PT.shape(),
projects: PT.arrayOf(PT.object).isRequired,
@@ -217,7 +372,15 @@ Editor.propTypes = {
selectedProjectId: PT.number.isRequired,
selectProject: PT.func.isRequired,
setPaymentAmount: PT.func.isRequired,
+ setCopilotPaymentAmount: PT.func.isRequired,
setPaymentAssignee: PT.func.isRequired,
+ setCopilot: PT.func.isRequired,
setPaymentDescription: PT.func.isRequired,
+ setSubmissionGuidelines: PT.func.isRequired,
setPaymentTitle: PT.func.isRequired,
+ technologyTags: PT.arrayOf(PT.shape()).isRequired,
+ addTechnologyTag: PT.func.isRequired,
+ removeTechnologyTag: PT.func.isRequired,
+ challengeTechnologyTags: PT.arrayOf(PT.shape()).isRequired,
+ memberTasks: PT.arrayOf(PT.shape()).isRequired,
};
diff --git a/src/shared/components/sandbox/payments/Editor/style.scss b/src/shared/components/sandbox/payments/Editor/style.scss
index 112bd0a..a0c3333 100644
--- a/src/shared/components/sandbox/payments/Editor/style.scss
+++ b/src/shared/components/sandbox/payments/Editor/style.scss
@@ -27,11 +27,23 @@
height: 30px;
}
+.fieldFlex {
+ display: flex;
+
+ + .fieldFlex {
+ margin-top: 10px;
+ }
+}
+
.field {
+ .field {
margin-top: 10px;
}
+ + .fieldFlex {
+ margin-top: 10px;
+ }
+
.label {
box-sizing: border-box;
display: inline-block;
@@ -120,3 +132,28 @@
padding-left: 130px;
text-align: center;
}
+
+.fakeTextArea {
+ width: 450px;
+ border: 1px solid #c0c0c0;
+ background-color: #aaaaab;
+ box-shadow: none;
+ border-radius: 4px;
+ padding: 5px;
+ min-height: 80px;
+ overflow: auto;
+ max-height: 450px;
+ margin-bottom: 10px;
+}
+
+.dateLabel {
+ box-sizing: border-box;
+ display: inline-block;
+ line-height: 40px;
+ text-align: right;
+ padding-right: 10px;
+ vertical-align: top;
+ font-size: 15px;
+ color: $tc-gray-80;
+ letter-spacing: 0;
+}
diff --git a/src/shared/components/sandbox/payments/Listing/PaymentRow/index.jsx b/src/shared/components/sandbox/payments/Listing/PaymentRow/index.jsx
index feb3c71..3cb4b08 100644
--- a/src/shared/components/sandbox/payments/Listing/PaymentRow/index.jsx
+++ b/src/shared/components/sandbox/payments/Listing/PaymentRow/index.jsx
@@ -39,6 +39,9 @@ export default function PaymentRow({ challenge }) {
{challenge.name}
+
+ { (new Date(_.get(challenge, 'updatedAt'))).toLocaleString() }
+ |
{`$${_.get(challenge, 'prizes[0]', '-')}`}
|
diff --git a/src/shared/components/sandbox/payments/Listing/PaymentStatus/index.jsx b/src/shared/components/sandbox/payments/Listing/PaymentStatus/index.jsx
index d5ab4a4..8e92ec7 100644
--- a/src/shared/components/sandbox/payments/Listing/PaymentStatus/index.jsx
+++ b/src/shared/components/sandbox/payments/Listing/PaymentStatus/index.jsx
@@ -37,6 +37,7 @@ PaymentStatus.propTypes = {
'COMPLETED',
'DELETED',
'PAUSED',
+ 'CANCELLED_CLIENT_REQUEST',
]).isRequired,
text: PT.string.isRequired,
};
diff --git a/src/shared/components/sandbox/payments/Listing/index.jsx b/src/shared/components/sandbox/payments/Listing/index.jsx
index dd7d83d..81670f6 100644
--- a/src/shared/components/sandbox/payments/Listing/index.jsx
+++ b/src/shared/components/sandbox/payments/Listing/index.jsx
@@ -7,6 +7,7 @@ import PT from 'prop-types';
import React from 'react';
import Select from 'components/Select';
import { PrimaryButton } from 'topcoder-react-ui-kit';
+import SwitchWithLabel from 'components/SwitchWithLabel';
import Background from '../Background';
import PaymentRow from './PaymentRow';
import style from './style.scss';
@@ -17,6 +18,8 @@ export default function Listing({
projects,
selectedProjectId,
selectProject,
+ toggleProjects,
+ hasActiveBillingAccount,
}) {
const selectedProjectIdNum = Number(selectedProjectId);
let content = memberTasks
@@ -42,6 +45,9 @@ export default function Listing({
Payment
+Payment Date
+ |
+
Amount
|
@@ -89,6 +95,11 @@ Project
value={Number(selectedProjectId)}
valueKey="id"
/>
+
+ { error
+ ? (
+ clearError()}
+ />
+ ) : undefined }
+
+ );
+}
+
+/**
+ * Default values for Props
+ */
+ErrorMessageContainer.defaultProps = {
+ error: null,
+};
+
+/**
+ * Prop Validation
+ */
+ErrorMessageContainer.propTypes = {
+ clearError: PT.func.isRequired,
+ error: PT.shape({
+ title: PT.string.isRequired,
+ details: PT.string.isRequired,
+ }),
+};
+
+/**
+ * Standard redux function, passes redux state into Container as props.
+ * Is passed to connect(), not called directly.
+ * @param {Object} state Redux state
+ * @return {Object}
+ */
+const mapStateToProps = state => ({
+ error: state.errors.alerts[0],
+});
+
+/**
+ * Standard redux function, passes redux actions into Container as props.
+ * Is passed to connect(), not called directly.
+ * @param {Function} dispatch Function to dispatch action to reducers
+ * @return {Object}
+ */
+const mapDispatchToProps = dispatch => ({
+ clearError: () => {
+ dispatch(actions.errors.clearError());
+ },
+});
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps,
+)(ErrorMessageContainer);
diff --git a/src/shared/containers/sandbox/payments/Editor/index.jsx b/src/shared/containers/sandbox/payments/Editor/index.jsx
index c66cda3..dc31c47 100644
--- a/src/shared/containers/sandbox/payments/Editor/index.jsx
+++ b/src/shared/containers/sandbox/payments/Editor/index.jsx
@@ -13,6 +13,8 @@ import Editor from 'components/sandbox/payments/Editor';
import LoadingIndicator from 'components/LoadingIndicator';
import PT from 'prop-types';
import React from 'react';
+import shortid from 'shortid';
+import Markdown from 'markdown-it';
import { STATE as PAGE_STATE } from 'actions/page/sandbox/payments/editor';
import { connect } from 'react-redux';
import { goToLogin } from 'utils/tc';
@@ -24,6 +26,7 @@ import './style.scss';
const { fireErrorMessage } = errors;
const getChallengeService = services.challenge.getService;
const getMembersService = services.members.getService;
+const md = new Markdown();
/**
* If given props have loaded project details with some billing accounts, this
@@ -79,6 +82,7 @@ class EditorContainer extends React.Component {
tokenV2,
tokenV3,
username,
+ getTechnologyTags,
} = this.props;
if (!authenticating && !tokenV3) return goToLogin('payments-tool');
if (username && username !== loadingProjectsForUsername) {
@@ -87,6 +91,9 @@ class EditorContainer extends React.Component {
if (!selectedProjectId && projects.length) {
selectProject(projects[0].id);
}
+ if (paymentId === 'new') {
+ getTechnologyTags(tokenV3);
+ }
handleProjectDetailsLoading(this.props);
selectFirstBillingAccountIfNecessary(this.props);
if (!challenge && paymentId !== 'new') {
@@ -103,18 +110,24 @@ class EditorContainer extends React.Component {
loadProjects,
paymentAmount,
// paymentAssignee,
- // paymentDescription,
+ paymentDescription,
+ submissionGuidelines,
paymentTitle,
projects,
selectedProjectId,
selectProject,
setPageState,
setPaymentAmount,
+ setCopilotPaymentAmount,
setPaymentAssignee,
+ setCopilot,
setPaymentDescription,
setPaymentTitle,
+ setSubmissionGuidelines,
tokenV3,
username,
+ loadMemberTasks,
+ memberTasks,
} = nextProps;
if (!authenticating && !tokenV3) return goToLogin('payments-tool');
const {
@@ -142,6 +155,9 @@ class EditorContainer extends React.Component {
setPageState(PAGE_STATE.NEW_PAYMENT);
if (selectedProjectId !== challenge.projectId) {
selectProject(challenge.projectId);
+ if (memberTasks.length === 0) {
+ loadMemberTasks(challenge.projectId, 0, tokenV3);
+ }
}
if (paymentAmount !== challenge.prizes[0]) {
setPaymentAmount(challenge.prizes[0] || 0);
@@ -149,8 +165,15 @@ class EditorContainer extends React.Component {
if (paymentTitle !== challenge.name) {
setPaymentTitle(challenge.name);
}
- setPaymentDescription('N/A');
+ if (paymentDescription !== challenge.detailedRequirements) {
+ setPaymentDescription(challenge.detailedRequirements);
+ }
+ if (submissionGuidelines !== challenge.finalSubmissionGuidelines) {
+ setSubmissionGuidelines(challenge.finalSubmissionGuidelines);
+ }
setPaymentAssignee('N/A');
+ setCopilotPaymentAmount(0);
+ setCopilot('N/A');
}
return undefined;
}
@@ -159,19 +182,39 @@ class EditorContainer extends React.Component {
* Handles the payment.
*/
async pay() {
+ const {
+ copilotPaymentAmount,
+ paymentAmount,
+ paymentTitle,
+ setPageState,
+ selectedBillingAccountId,
+ selectedProjectId,
+ tokenV3,
+ challengeTechnologyTags,
+ } = this.props;
try {
- const {
- paymentAmount,
- paymentAssignee,
+ let {
paymentDescription,
- paymentTitle,
- setPageState,
- selectedBillingAccountId,
- selectedProjectId,
- tokenV3,
+ submissionGuidelines,
+ copilot,
+ paymentAssignee,
} = this.props;
+
+ const membersService = getMembersService(tokenV3);
+ paymentDescription = md.render(paymentDescription);
+ submissionGuidelines = md.render(submissionGuidelines);
+ const technologies = challengeTechnologyTags.map(t => (
+ { name: t.name, id: parseInt(t.id, 10) }
+ ));
setPageState(PAGE_STATE.WAITING_PAYMENT_DRAFT);
const challengeService = getChallengeService(tokenV3);
+ let copilotId = 0;
+ if (!paymentAssignee) paymentAssignee = copilot;
+ if (copilot) {
+ copilot = await membersService.getMemberInfo(copilot);
+ copilotId = copilot.userId ? copilot.userId : 0;
+ }
+
const challenge = await challengeService.createTask(
selectedProjectId,
selectedBillingAccountId,
@@ -179,10 +222,13 @@ class EditorContainer extends React.Component {
paymentDescription,
paymentAssignee,
paymentAmount,
+ submissionGuidelines,
+ copilotId,
+ copilotPaymentAmount,
+ technologies,
);
setPageState(PAGE_STATE.WAITING_PAYMENT_ACTIVATION);
await challengeService.activate(challenge.id);
- const membersService = getMembersService(tokenV3);
setPageState(PAGE_STATE.WAITING_PAYMENT_CLOSURE);
const member = await membersService.getMemberInfo(paymentAssignee);
if (member) {
@@ -190,6 +236,7 @@ class EditorContainer extends React.Component {
}
setPageState(PAGE_STATE.PAID);
} catch (error) {
+ setPageState(PAGE_STATE.NEW_PAYMENT);
logger.error(error);
fireErrorMessage(
'Failed to proceed the payment',
@@ -204,14 +251,24 @@ class EditorContainer extends React.Component {
setMemberInputPopupVisible,
setPageState,
setPaymentAmount,
+ setCopilotPaymentAmount,
+ setCopilotInputKeyword,
+ setCopilotInputPopupVisible,
setPaymentAssignee,
setPaymentDescription,
+ setSubmissionGuidelines,
+ setCopilot,
setPaymentTitle,
} = this.props;
setPageState(PAGE_STATE.NEW_PAYMENT);
setPaymentAmount(0);
+ setCopilotPaymentAmount(0);
setPaymentAssignee('');
+ setCopilot('');
+ setCopilotInputKeyword('');
+ setCopilotInputPopupVisible(false);
setPaymentDescription('');
+ setSubmissionGuidelines('');
setPaymentTitle('');
setMemberInputKeyword('');
setMemberInputPopupVisible(false);
@@ -228,11 +285,22 @@ class EditorContainer extends React.Component {
setMemberInputKeyword,
memberInputSelected,
setMemberInputSelected,
+ copilotSuggestions,
+ getCopilotSuggestions,
+ copilotInputPopupVisible,
+ setCopilotInputPopupVisible,
+ copilotInputKeyword,
+ setCopilotInputKeyword,
+ copilotInputSelected,
+ setCopilotInputSelected,
pageState,
paymentAmount,
+ copilotPaymentAmount,
paymentAssignee,
+ copilot,
paymentId,
paymentDescription,
+ submissionGuidelines,
paymentTitle,
projectDetails,
projects,
@@ -240,10 +308,18 @@ class EditorContainer extends React.Component {
selectedProjectId,
selectProject,
setPaymentAmount,
+ setCopilotPaymentAmount,
setPaymentAssignee,
+ setCopilot,
setPaymentDescription,
+ setSubmissionGuidelines,
setPaymentTitle,
tokenV3,
+ technologyTags,
+ addTechnologyTag,
+ removeTechnologyTag,
+ challengeTechnologyTags,
+ memberTasks,
} = this.props;
/* TODO: This render function becomes too complex for a container,
* most of this should be moved to the Editor component itself. */
@@ -275,10 +351,14 @@ class EditorContainer extends React.Component {
);
}
if (pageState === PAGE_STATE.PAID) {
+ const competitior = paymentAssignee !== '' ? paymentAssignee : copilot;
return (
this.resetPaymentData()}
/>
);
@@ -296,10 +376,21 @@ class EditorContainer extends React.Component {
setMemberInputKeyword={setMemberInputKeyword}
memberInputSelected={memberInputSelected}
setMemberInputSelected={setMemberInputSelected}
+ copilotSuggestions={copilotSuggestions}
+ getCopilotSuggestions={keyword => getCopilotSuggestions(keyword, tokenV3)}
+ copilotInputPopupVisible={copilotInputPopupVisible}
+ setCopilotInputPopupVisible={setCopilotInputPopupVisible}
+ copilotInputKeyword={copilotInputKeyword}
+ setCopilotInputKeyword={setCopilotInputKeyword}
+ copilotInputSelected={copilotInputSelected}
+ setCopilotInputSelected={setCopilotInputSelected}
neu={paymentId === 'new'}
paymentAmount={paymentAmount}
+ copilotPaymentAmount={copilotPaymentAmount}
paymentAssignee={paymentAssignee}
+ copilot={copilot}
paymentDescription={paymentDescription}
+ submissionGuidelines={submissionGuidelines}
paymentTitle={paymentTitle}
projectDetails={projectDetails}
projects={projects}
@@ -307,9 +398,17 @@ class EditorContainer extends React.Component {
selectedProjectId={selectedProjectId}
selectProject={selectProject}
setPaymentAmount={setPaymentAmount}
+ setCopilotPaymentAmount={setCopilotPaymentAmount}
setPaymentAssignee={setPaymentAssignee}
+ setCopilot={setCopilot}
setPaymentDescription={setPaymentDescription}
+ setSubmissionGuidelines={setSubmissionGuidelines}
setPaymentTitle={setPaymentTitle}
+ technologyTags={technologyTags}
+ addTechnologyTag={addTechnologyTag}
+ removeTechnologyTag={removeTechnologyTag}
+ challengeTechnologyTags={challengeTechnologyTags}
+ memberTasks={memberTasks}
/>
);
}
@@ -336,11 +435,22 @@ EditorContainer.propTypes = {
setMemberInputKeyword: PT.func.isRequired,
memberInputSelected: PT.shape().isRequired,
setMemberInputSelected: PT.func.isRequired,
+ copilotSuggestions: PT.arrayOf(PT.shape()).isRequired,
+ getCopilotSuggestions: PT.func.isRequired,
+ copilotInputPopupVisible: PT.bool.isRequired,
+ setCopilotInputPopupVisible: PT.func.isRequired,
+ copilotInputKeyword: PT.string.isRequired,
+ setCopilotInputKeyword: PT.func.isRequired,
+ copilotInputSelected: PT.shape().isRequired,
+ setCopilotInputSelected: PT.func.isRequired,
pageState: PT.oneOf(_.values(PAGE_STATE)).isRequired,
paymentAmount: PT.number.isRequired,
+ copilotPaymentAmount: PT.number.isRequired,
paymentAssignee: PT.string.isRequired,
+ copilot: PT.string.isRequired,
paymentId: PT.string.isRequired,
paymentDescription: PT.string.isRequired,
+ submissionGuidelines: PT.string.isRequired,
paymentTitle: PT.string.isRequired,
projectDetails: PT.shape({
billingAccountIds: PT.arrayOf(PT.number).isRequired,
@@ -353,15 +463,25 @@ EditorContainer.propTypes = {
setPageState: PT.func.isRequired,
setPaymentAmount: PT.func.isRequired,
setPaymentAssignee: PT.func.isRequired,
+ setCopilot: PT.func.isRequired,
setPaymentDescription: PT.func.isRequired,
+ setSubmissionGuidelines: PT.func.isRequired,
setPaymentTitle: PT.func.isRequired,
tokenV2: PT.string.isRequired,
tokenV3: PT.string.isRequired,
username: PT.string.isRequired,
+ getTechnologyTags: PT.func.isRequired,
+ setCopilotPaymentAmount: PT.func.isRequired,
+ technologyTags: PT.arrayOf(PT.shape()).isRequired,
+ addTechnologyTag: PT.func.isRequired,
+ removeTechnologyTag: PT.func.isRequired,
+ challengeTechnologyTags: PT.arrayOf(PT.shape()).isRequired,
+ loadMemberTasks: PT.func.isRequired,
+ memberTasks: PT.arrayOf(PT.object).isRequired,
};
function mapStateToProps(state, ownProps) {
- const { auth, direct } = state;
+ const { auth, direct, memberTasks } = state;
const page = state.page.sandbox.payments.editor;
let challenge;
@@ -384,11 +504,18 @@ function mapStateToProps(state, ownProps) {
memberInputPopupVisible: page.memberInputPopupVisible,
memberInputKeyword: page.memberInputKeyword,
memberInputSelected: page.memberInputSelected,
+ copilotSuggestions: page.copilotSuggestions,
+ copilotInputPopupVisible: page.copilotInputPopupVisible,
+ copilotInputKeyword: page.copilotInputKeyword,
+ copilotInputSelected: page.copilotInputSelected,
pageState: page.pageState,
paymentAmount: page.paymentAmount,
paymentAssignee: page.paymentAssignee,
+ copilotPaymentAmount: page.copilotPaymentAmount,
+ copilot: page.copilot,
paymentId,
paymentDescription: page.paymentDescription,
+ submissionGuidelines: page.submissionGuidelines,
paymentTitle: page.paymentTitle,
projectDetails,
projects: direct.projects,
@@ -397,13 +524,21 @@ function mapStateToProps(state, ownProps) {
tokenV2: auth.tokenV2,
tokenV3: auth.tokenV3,
username: _.get(auth, 'user.handle', ''),
+ technologyTags: page.technologyTags,
+ challengeTechnologyTags: page.challengeTechnologyTags,
+ memberTasks: memberTasks.tasks,
};
}
function mapDispatchToProps(dispatch) {
- const { challenge, direct } = actions;
+ const { challenge, direct, memberTasks } = actions;
const page = actions.page.sandbox.payments.editor;
return {
+ loadMemberTasks: (projectId, pageNum, tokenV3) => {
+ const uuid = shortid();
+ dispatch(memberTasks.getInit(uuid, pageNum));
+ dispatch(memberTasks.getDone(uuid, projectId, pageNum, tokenV3));
+ },
getChallenge: (id, tokenV3, tokenV2) => {
dispatch(challenge.getDetailsInit(id));
dispatch(challenge.getDetailsDone(id, tokenV3, tokenV2));
@@ -422,16 +557,33 @@ function mapDispatchToProps(dispatch) {
dispatch(page.getMemberSuggestionsDone(keyword, tokenV3));
}
},
+ getTechnologyTags: (tokenV3) => {
+ dispatch(page.getTechnologyTags(tokenV3));
+ },
setMemberInputPopupVisible: visible => dispatch(page.setMemberInputPopupVisible(visible)),
setMemberInputKeyword: keyword => dispatch(page.setMemberInputKeyword(keyword)),
setMemberInputSelected: member => dispatch(page.setMemberInputSelected(member)),
+ getCopilotSuggestions: (keyword, tokenV3) => {
+ if (keyword.length >= AUTOCOMPLETE_TRIGGER_LENGTH) {
+ dispatch(page.getCopilotSuggestionsInit(keyword));
+ dispatch(page.getCopilotSuggestionsDone(keyword, tokenV3));
+ }
+ },
+ setCopilotInputPopupVisible: visible => dispatch(page.setCopilotInputPopupVisible(visible)),
+ setCopilotInputKeyword: keyword => dispatch(page.setCopilotInputKeyword(keyword)),
+ setCopilotInputSelected: member => dispatch(page.setCopilotInputSelected(member)),
selectBillingAccount: accountId => dispatch(page.selectBillingAccount(accountId)),
selectProject: projectId => dispatch(page.selectProject(projectId)),
setPageState: state => dispatch(page.setPageState(state)),
setPaymentAmount: arg => dispatch(page.setPaymentAmount(arg)),
setPaymentAssignee: arg => dispatch(page.setPaymentAssignee(arg)),
+ setCopilotPaymentAmount: arg => dispatch(page.setCopilotPaymentAmount(arg)),
+ setCopilot: arg => dispatch(page.setCopilot(arg)),
setPaymentDescription: arg => dispatch(page.setPaymentDescription(arg)),
+ setSubmissionGuidelines: arg => dispatch(page.setSubmissionGuidelines(arg)),
setPaymentTitle: title => dispatch(page.setPaymentTitle(title)),
+ addTechnologyTag: tag => dispatch(page.addTechnologyTag(tag)),
+ removeTechnologyTag: i => dispatch(page.removeTechnologyTag(i)),
};
}
diff --git a/src/shared/containers/sandbox/payments/Listing.jsx b/src/shared/containers/sandbox/payments/Listing.jsx
index a2c3d64..22340c3 100644
--- a/src/shared/containers/sandbox/payments/Listing.jsx
+++ b/src/shared/containers/sandbox/payments/Listing.jsx
@@ -25,10 +25,11 @@ class ListingContainer extends React.Component {
loadProjects,
tokenV3,
username,
+ hasActiveBillingAccount,
} = this.props;
if (!authenticating && !tokenV3) return goToLogin('payments-tool');
if (username && username !== loadingProjectsForUsername) {
- loadProjects(tokenV3);
+ loadProjects(tokenV3, hasActiveBillingAccount);
}
return undefined;
}
@@ -42,14 +43,19 @@ class ListingContainer extends React.Component {
selectedProjectId,
tokenV3,
username: nextUsername,
+ hasActiveBillingAccount: nextHasActiveBillingAccount,
} = nextProps;
const {
username,
+ hasActiveBillingAccount,
} = this.props;
if (!authenticating && !tokenV3) return goToLogin('payments-tool');
if (nextUsername !== username && nextUsername
&& nextUsername !== loadingProjectsForUsername) {
- loadProjects(tokenV3);
+ loadProjects(tokenV3, nextHasActiveBillingAccount);
+ }
+ if (nextHasActiveBillingAccount !== hasActiveBillingAccount) {
+ loadProjects(tokenV3, nextHasActiveBillingAccount);
}
if (!selectedProjectId && projects.length) {
selectProjectAndLoadMemberTasks(projects[0].id, nextProps);
@@ -65,6 +71,8 @@ class ListingContainer extends React.Component {
projects,
selectedProjectId,
tokenV3,
+ toggleProjects,
+ hasActiveBillingAccount,
} = this.props;
if ((loadingProjectsForUsername && !projects.length) || !tokenV3) {
@@ -77,6 +85,8 @@ class ListingContainer extends React.Component {
memberTasks={memberTasks}
projects={projects}
selectedProjectId={selectedProjectId}
+ toggleProjects={toggleProjects}
+ hasActiveBillingAccount={hasActiveBillingAccount}
selectProject={projectId => selectProjectAndLoadMemberTasks(projectId, this.props)}
/>
);
@@ -86,7 +96,9 @@ class ListingContainer extends React.Component {
ListingContainer.propTypes = {
authenticating: PT.bool.isRequired,
loadingMemberTasks: PT.bool.isRequired,
+ hasActiveBillingAccount: PT.bool.isRequired,
loadProjects: PT.func.isRequired,
+ toggleProjects: PT.func.isRequired,
loadingProjectsForUsername: PT.string.isRequired,
memberTasks: PT.arrayOf(PT.object).isRequired,
projects: PT.arrayOf(PT.object).isRequired,
@@ -111,6 +123,7 @@ function mapStateToProps(state) {
memberTasks: memberTasks.tasks,
projects: direct.projects,
selectedProjectId: page.selectedProjectId,
+ hasActiveBillingAccount: page.hasActiveBillingAccount,
tokenV3: auth.tokenV3,
username: _.get(auth, 'user.handle', ''),
};
@@ -130,9 +143,12 @@ function mapDispatchToProps(dispatch) {
dispatch(memberTasks.getInit(uuid, pageNum));
dispatch(memberTasks.getDone(uuid, projectId, pageNum, tokenV3));
},
- loadProjects: (tokenV3) => {
+ toggleProjects: (toggle) => {
+ dispatch(payments.listing.toggleProjects(toggle));
+ },
+ loadProjects: (tokenV3, hasBillAc) => {
dispatch(direct.getUserProjectsInit(tokenV3));
- dispatch(direct.getUserProjectsDone(tokenV3));
+ dispatch(direct.getUserProjectsDone(tokenV3, hasBillAc));
},
selectProject: (projectId) => {
dispatch(payments.editor.selectProject(projectId));
diff --git a/src/shared/reducers/page/sandbox/payments/editor.js b/src/shared/reducers/page/sandbox/payments/editor.js
index f273e79..72e1c04 100644
--- a/src/shared/reducers/page/sandbox/payments/editor.js
+++ b/src/shared/reducers/page/sandbox/payments/editor.js
@@ -56,6 +56,56 @@ function onSetMemberInputSelected(state, { payload }) {
return { ...state, memberInputSelected: payload, memberInputKeyword: payload.handle };
}
+/**
+ * Initialize API request for copilot suggestions
+ * @param {Object} state
+ * @param {Array} action.payload Partial name
+ * @return {Object} New state.
+ */
+function onGetCopilotSuggestionsInit(state, { payload }) {
+ return { ...state, getCopilotSuggestionsForKeyword: payload };
+}
+
+/**
+ * Finish API request for copilot suggestions
+ * @param {Object} state
+ * @param {Array} action.payload Array of potential copilot matches
+ * @return {Object} New state.
+ */
+function onGetCopilotSuggestionsDone(state, { payload }) {
+ return { ...state, copilotSuggestions: payload, getCopilotSuggestionsForKeyword: '' };
+}
+
+/**
+ * Sets visibility of copilot input search popup
+ * @param {Object} state
+ * @param {Boolean} action.payload If the popup is visible
+ * @return {Object} New state.
+ */
+function onSetCopilotInputPopupVisible(state, { payload }) {
+ return { ...state, copilotInputPopupVisible: payload };
+}
+
+/**
+ * Sets the keyword/partial name that user has typed for Copilot
+ * @param {Object} state
+ * @param {String} action.payload Keyword/partial copilot name that user is typing
+ * @return {Object} New state.
+ */
+function onSetCopilotInputKeyword(state, { payload }) {
+ return { ...state, copilotInputKeyword: payload };
+}
+
+/**
+ * Finish API request for copilot suggestions
+ * @param {Object} state
+ * @param {Object} action.payload Copilot that the user has selected
+ * @return {Object} New state.
+ */
+function onSetCopilotInputSelected(state, { payload }) {
+ return { ...state, copilotInputSelected: payload, copilotInputKeyword: payload.handle };
+}
+
/**
* Selects the specified billing account.
* @param {Object} state
@@ -106,6 +156,26 @@ function onSetPaymentAssignee(state, { payload }) {
return { ...state, paymentAssignee: payload };
}
+/**
+ * Sets copilot payment amount.
+ * @param {Object} state
+ * @param {Number} action.payload Copilot payment amount.
+ * @return {Object} New state.
+ */
+function onSetCopilotPaymentAmount(state, { payload }) {
+ return { ...state, copilotPaymentAmount: payload };
+}
+
+/**
+ * Sets the copilot.
+ * @param {Object} state
+ * @param {String} action.payload copilot.
+ * @return {Object} New state.
+ */
+function onSetCopilot(state, { payload }) {
+ return { ...state, copilot: payload };
+}
+
/**
* Sets the payment description.
* @param {Object} state
@@ -126,6 +196,32 @@ function onSetPaymentTitle(state, { payload }) {
return { ...state, paymentTitle: payload };
}
+/**
+ * Sets the submission guidelines.
+ * @param {Object} state
+ * @param {String} action.payload
+ * @return {Object} New state.
+ */
+function onSetSubmissionGuidelines(state, { payload }) {
+ return { ...state, submissionGuidelines: payload };
+}
+
+function onGetTechnologyTags(state, { payload }) {
+ return { ...state, technologyTags: payload };
+}
+
+function onAddTechnologyTag(state, { payload }) {
+ return { ...state, challengeTechnologyTags: [...state.challengeTechnologyTags, payload] };
+}
+
+function onRemoveTechnologyTag(state, { payload }) {
+ return {
+ ...state,
+ challengeTechnologyTags:
+ state.challengeTechnologyTags.filter((tag, index) => index !== payload),
+ };
+}
+
/**
* Creates reducer with the specified initial state, or default state otherwise.
* @param {Object} state Optional. Initial state. If not given, default state
@@ -140,26 +236,47 @@ function create(state = {}) {
[a.setMemberInputPopupVisible]: onSetMemberInputPopupVisible,
[a.setMemberInputKeyword]: onSetMemberInputKeyword,
[a.setMemberInputSelected]: onSetMemberInputSelected,
+ [a.getCopilotSuggestionsInit]: onGetCopilotSuggestionsInit,
+ [a.getCopilotSuggestionsDone]: onGetCopilotSuggestionsDone,
+ [a.setCopilotInputPopupVisible]: onSetCopilotInputPopupVisible,
+ [a.setCopilotInputKeyword]: onSetCopilotInputKeyword,
+ [a.setCopilotInputSelected]: onSetCopilotInputSelected,
[a.selectBillingAccount]: onSelectBillingAccount,
[a.selectProject]: onSelectProject,
[a.setPageState]: onSetPageState,
[a.setPaymentAmount]: onSetPaymentAmount,
[a.setPaymentAssignee]: onSetPaymentAssignee,
+ [a.setCopilotPaymentAmount]: onSetCopilotPaymentAmount,
+ [a.setCopilot]: onSetCopilot,
[a.setPaymentDescription]: onSetPaymentDescription,
[a.setPaymentTitle]: onSetPaymentTitle,
+ [a.setSubmissionGuidelines]: onSetSubmissionGuidelines,
+ [a.getTechnologyTags]: onGetTechnologyTags,
+ [a.addTechnologyTag]: onAddTechnologyTag,
+ [a.removeTechnologyTag]: onRemoveTechnologyTag,
}, _.defaults(state, {
getMemberSuggestionsForKeyword: '',
memberSuggestions: [],
memberInputPopupVisible: false,
memberInputKeyword: '',
memberInputSelected: {},
+ getCopilotSuggestionsForKeyword: '',
+ copilotSuggestions: [],
+ copilotInputPopupVisible: false,
+ copilotInputKeyword: '',
+ copilotInputSelected: {},
pageState: STATE.NEW_PAYMENT,
paymentAmount: 0,
paymentAssignee: '',
+ copilotPaymentAmount: 0,
+ copilot: '',
paymentDescription: '',
paymentTitle: '',
+ submissionGuidelines: '',
selectedBillingAccountId: 0,
selectedProjectId: 0,
+ technologyTags: [],
+ challengeTechnologyTags: [],
}));
}
diff --git a/src/shared/reducers/page/sandbox/payments/listing.js b/src/shared/reducers/page/sandbox/payments/listing.js
index 5210e24..cb62e6e 100644
--- a/src/shared/reducers/page/sandbox/payments/listing.js
+++ b/src/shared/reducers/page/sandbox/payments/listing.js
@@ -16,6 +16,10 @@ function onSelectProject(state, { payload }) {
return { ...state, selectedProjectId: payload };
}
+function onToggleProjects(state, { payload }) {
+ return { ...state, hasActiveBillingAccount: payload };
+}
+
/**
* Creates reducer with the specified initial state, or default state otherwise.
* @param {Object} state Optional. Initial state. If not given, default state
@@ -26,8 +30,10 @@ function create(state = {}) {
const a = actions.page.sandbox.payments.listing;
return handleActions({
[a.selectProject]: onSelectProject,
+ [a.toggleProjects]: onToggleProjects,
}, _.defaults(state, {
selectedProjectId: 0,
+ hasActiveBillingAccount: true,
}));
}
diff --git a/src/styles/_global/_forms.scss b/src/styles/_global/_forms.scss
index 8de09ba..348b1c5 100644
--- a/src/styles/_global/_forms.scss
+++ b/src/styles/_global/_forms.scss
@@ -397,3 +397,81 @@ textarea:focus {
color: $tc-gray-80;
}
}
+
+// Styling fot react input tags
+
+:global {
+ .ReactTags__selected {
+ display: flex;
+ flex-wrap: wrap;
+ }
+
+ .ReactTags__tagInputField {
+ box-shadow: none;
+ }
+
+ .ReactTags__tags {
+ border: 1px solid #c0c0c0;
+ border-radius: 2px;
+ padding-top: 5px;
+ width: 450px;
+ }
+
+ .ReactTags__tag {
+ border: 1px solid #ddd;
+ background: #eee;
+ font-size: 18px;
+ display: block;
+ padding: 5px 10px;
+ margin: 0 5px 5px 5px;
+ cursor: move;
+ border-radius: 2px;
+ vertical-align: middle;
+ line-height: 30px;
+ }
+
+ .ReactTags__tagInput {
+ width: 100px;
+ }
+
+ div.ReactTags__tagInput input.ReactTags__tagInputField,
+ div.ReactTags__tagInput input.ReactTags__tagInputField:hover,
+ div.ReactTags__tagInput input.ReactTags__tagInputField:focus {
+ border: none;
+ box-shadow: none;
+ }
+
+ div.ReactTags__suggestions {
+ position: absolute;
+ }
+
+ div.ReactTags__suggestions ul {
+ list-style-type: none;
+ box-shadow: 0.05em 0.01em 0.5em rgba(0, 0, 0, 0.2);
+ background: white;
+ width: 200px;
+ }
+
+ div.ReactTags__suggestions li {
+ border-bottom: 1px solid #ddd;
+ padding: 5px 10px;
+ margin: 0;
+ }
+
+ div.ReactTags__suggestions li mark {
+ text-decoration: underline;
+ background: none;
+ font-weight: 600;
+ }
+
+ div.ReactTags__suggestions ul li.ReactTags__activeSuggestion {
+ background: #c0c0c0;
+ cursor: pointer;
+ }
+
+ div.ReactTags__selected a.ReactTags__remove {
+ color: #aaa;
+ margin-left: 5px;
+ cursor: pointer;
+ }
+}
|