diff --git a/src/shared/components/SubmissionManagement/Submission/index.jsx b/src/shared/components/SubmissionManagement/Submission/index.jsx
index c2f6c8816e..3c82660f7f 100644
--- a/src/shared/components/SubmissionManagement/Submission/index.jsx
+++ b/src/shared/components/SubmissionManagement/Submission/index.jsx
@@ -42,6 +42,7 @@ export default function Submission(props) {
{submissionObject.id}
+ {submissionObject.legacySubmissionId}
{submissionObject.type}
@@ -118,6 +119,7 @@ Submission.defaultProps = {
Submission.propTypes = {
submissionObject: PT.shape({
id: PT.string,
+ legacySubmissionId: PT.string,
warpreviewnings: PT.string,
screening: PT.shape({
status: PT.string,
diff --git a/src/shared/components/SubmissionManagement/Submission/styles.scss b/src/shared/components/SubmissionManagement/Submission/styles.scss
index 981c203383..07ffd7f033 100644
--- a/src/shared/components/SubmissionManagement/Submission/styles.scss
+++ b/src/shared/components/SubmissionManagement/Submission/styles.scss
@@ -77,6 +77,10 @@ $submission-space-50: $base-unit * 10;
}
}
+ .legacy-id {
+ color: $tc-gray-50;
+ }
+
.status-col {
text-align: center;
diff --git a/src/shared/components/TopcoderFooter/style.scss b/src/shared/components/TopcoderFooter/style.scss
index a5d20be0b4..523667a4d8 100644
--- a/src/shared/components/TopcoderFooter/style.scss
+++ b/src/shared/components/TopcoderFooter/style.scss
@@ -72,6 +72,7 @@
font-weight: 400;
line-height: 21px;
position: relative;
+ z-index: 0;
@include xs-to-sm {
padding: 30px 30px 21px 30px;
diff --git a/src/shared/components/challenge-detail/Header/ChallengeTags.jsx b/src/shared/components/challenge-detail/Header/ChallengeTags.jsx
index bbea412300..67c51669db 100644
--- a/src/shared/components/challenge-detail/Header/ChallengeTags.jsx
+++ b/src/shared/components/challenge-detail/Header/ChallengeTags.jsx
@@ -62,7 +62,9 @@ export default function ChallengeTags(props) {
challengeType
&& (
setImmediate(() => setChallengeListingFilter(challengeType.id))
+ onClick={() => (
+ setImmediate(() => setChallengeListingFilter({ types: [challengeType.id] }))
+ )
}
to={`${challengesUrl}?filter[types][0]=${encodeURIComponent(challengeType.id)}`}
>
diff --git a/src/shared/components/challenge-listing/Tooltips/ProgressBarTooltip/index.jsx b/src/shared/components/challenge-listing/Tooltips/ProgressBarTooltip/index.jsx
index 432afcde8b..65aac9c5f9 100644
--- a/src/shared/components/challenge-listing/Tooltips/ProgressBarTooltip/index.jsx
+++ b/src/shared/components/challenge-listing/Tooltips/ProgressBarTooltip/index.jsx
@@ -97,17 +97,17 @@ function Tip(props) {
const allPhases = c.phases || [];
const endPhaseDate = Math.max(...allPhases.map(d => phaseEndDate(d)));
- const registrationPhase = allPhases.find(phase => phase.name === 'Registration');
- const submissionPhase = allPhases.find(phase => phase.name === 'Submission');
- const checkpointPhase = allPhases.find(phase => phase.name === 'Checkpoint Submission');
+ const registrationPhase = allPhases.find(phase => phase.name === 'Registration') || {};
+ const submissionPhase = allPhases.find(phase => phase.name === 'Submission') || {};
+ const checkpointPhase = allPhases.find(phase => phase.name === 'Checkpoint Submission') || {};
- if (registrationPhase) {
+ if (!_.isEmpty(registrationPhase)) {
steps.push({
date: phaseStartDate(registrationPhase),
name: 'Start',
});
}
- if (checkpointPhase) {
+ if (!_.isEmpty(checkpointPhase)) {
steps.push({
date: phaseEndDate(checkpointPhase),
name: 'Checkpoint',
@@ -119,7 +119,7 @@ function Tip(props) {
date: phaseEndDate(iterativeReviewPhase),
name: 'Iterative Review',
});
- } else if (submissionPhase) {
+ } else if (!_.isEmpty(submissionPhase)) {
steps.push({
date: phaseEndDate(submissionPhase),
name: 'Submission',
diff --git a/src/shared/components/examples/GUIKit/Checkbox/index.jsx b/src/shared/components/examples/GUIKit/Checkbox/index.jsx
new file mode 100644
index 0000000000..2a0d7d4066
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Checkbox/index.jsx
@@ -0,0 +1,189 @@
+import React from 'react';
+import Checkbox from 'components/GUIKit/Checkbox';
+
+import './style.scss';
+
+export default function CheckboxExample() {
+ const checkValues = [
+ {
+ label: '',
+ key: 1,
+ checked: false,
+ size: 'lg',
+ },
+ {
+ label: '',
+ key: 2,
+ checked: true,
+ size: 'lg',
+ },
+ {
+ label: '',
+ key: 3,
+ checked: false,
+ size: 'sm',
+ },
+ {
+ label: '',
+ key: 4,
+ checked: true,
+ size: 'sm',
+ },
+ {
+ label: '',
+ key: 5,
+ checked: false,
+ size: 'xs',
+ },
+ {
+ label: '',
+ key: 6,
+ checked: true,
+ size: 'xs',
+ },
+ {
+ label: 'Option 1',
+ key: 7,
+ checked: true,
+ size: 'lg',
+ },
+ {
+ label: 'Option 1',
+ key: 8,
+ checked: true,
+ size: 'sm',
+ },
+ {
+ label: 'Option 1',
+ key: 9,
+ checked: true,
+ size: 'xs',
+ },
+ {
+ label: 'Option 2',
+ key: 10,
+ checked: false,
+ size: 'lg',
+ },
+ {
+ label: 'Option 2',
+ key: 11,
+ checked: false,
+ size: 'sm',
+ },
+ {
+ label: 'Option 2',
+ key: 12,
+ checked: false,
+ size: 'xs',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+
+
+
+ onChange(checked)}
+ size={checkValues[0].size}
+ checked={checkValues[0].checked}
+ />
+
+
onChange(checked)}
+ size={checkValues[1].size}
+ checked={checkValues[1].checked}
+ />
+
+
+
+ onChange(checked)}
+ size={checkValues[2].size}
+ checked={checkValues[2].checked}
+ />
+
+
onChange(checked)}
+ size={checkValues[3].size}
+ checked={checkValues[3].checked}
+ />
+
+
+
+ onChange(checked)}
+ size={checkValues[4].size}
+ checked={checkValues[4].checked}
+ />
+
+
onChange(checked)}
+ size={checkValues[5].size}
+ checked={checkValues[5].checked}
+ />
+
+
+
+
Examples usage
+
+
+
+ onChange(checked)}
+ size={checkValues[6].size}
+ checked={checkValues[6].checked}
+ />
+ {checkValues[6].label}
+
+
+ onChange(checked)}
+ size={checkValues[7].size}
+ checked={checkValues[7].checked}
+ />
+ {checkValues[7].label}
+
+
+ onChange(checked)}
+ size={checkValues[8].size}
+ checked={checkValues[8].checked}
+ />
+ {checkValues[8].label}
+
+
+
+
+ onChange(checked)}
+ size={checkValues[9].size}
+ checked={checkValues[9].checked}
+ />
+ {checkValues[9].label}
+
+
+ onChange(checked)}
+ size={checkValues[10].size}
+ checked={checkValues[10].checked}
+ />
+ {checkValues[10].label}
+
+
+ onChange(checked)}
+ size={checkValues[11].size}
+ checked={checkValues[11].checked}
+ />
+ {checkValues[11].label}
+
+
+
+
+
+ );
+}
diff --git a/src/shared/components/examples/GUIKit/Checkbox/style.scss b/src/shared/components/examples/GUIKit/Checkbox/style.scss
new file mode 100644
index 0000000000..06164ce657
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Checkbox/style.scss
@@ -0,0 +1,56 @@
+@import '~styles/mixins';
+
+.container {
+ display: flex;
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+}
+
+.grid1,
+.grid2,
+.grid21 {
+ display: flex;
+ flex-direction: column;
+}
+
+.grid11,
+.grid211,
+.grid2111 {
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.grid111 {
+ display: flex;
+ width: 50px;
+}
+
+.grid11 {
+ margin-top: 20px;
+}
+
+.grid1 {
+ margin-right: 100px;
+}
+
+.grid2 {
+ margin-top: 20px;
+}
+
+.grid2Label {
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-bottom: 20px;
+}
+
+.grid2111 {
+ margin-right: 60px;
+ margin-bottom: 10px;
+
+ span {
+ margin-left: 15px;
+ font-size: 14px;
+ }
+}
diff --git a/src/shared/components/examples/GUIKit/Datepicker/index.jsx b/src/shared/components/examples/GUIKit/Datepicker/index.jsx
new file mode 100644
index 0000000000..b7d0309f1f
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Datepicker/index.jsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import Datepicker from 'components/GUIKit/Datepicker';
+
+import './style.scss';
+
+export default function DatepickerExample() {
+ const values = [
+ {
+ key: 2,
+ placeholder: 'Available From',
+ label: '',
+ value: null,
+ required: false,
+ errorMsg: '',
+ sectionTitle: 'Empty',
+ },
+ {
+ key: 3,
+ placeholder: 'Available From',
+ label: 'Available From',
+ value: new Date(),
+ required: false,
+ errorMsg: '',
+ sectionTitle: 'Filled',
+ },
+ {
+ key: 4,
+ placeholder: 'Available From',
+ label: 'Available From',
+ value: new Date(),
+ required: false,
+ errorMsg: 'This date is wrong. Please check it again.',
+ sectionTitle: 'Error',
+ },
+ {
+ key: 5,
+ placeholder: 'Available From',
+ label: 'Available From',
+ value: new Date(),
+ required: true,
+ errorMsg: '',
+ sectionTitle: 'Require',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+ {values.map(value => (
+
+ {value.sectionTitle}
+
+
+ ))}
+
+ );
+}
diff --git a/src/shared/components/examples/GUIKit/Datepicker/style.scss b/src/shared/components/examples/GUIKit/Datepicker/style.scss
new file mode 100644
index 0000000000..7bb011b166
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Datepicker/style.scss
@@ -0,0 +1,31 @@
+@import '~styles/mixins';
+
+.container {
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+
+ :global {
+ .datepickerContainer {
+ flex-grow: 1;
+ }
+ }
+}
+
+.rowItem {
+ margin-top: 20px;
+ display: flex;
+ align-items: flex-start;
+ max-width: 520px;
+ flex-shrink: 0;
+}
+
+.sectionTitle {
+ margin-right: 24px;
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-left: 27px;
+ width: 50px;
+ flex-shrink: 0;
+ margin-top: 30px;
+}
diff --git a/src/shared/components/examples/GUIKit/Dropdown/index.jsx b/src/shared/components/examples/GUIKit/Dropdown/index.jsx
new file mode 100644
index 0000000000..2eb0b7d937
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Dropdown/index.jsx
@@ -0,0 +1,87 @@
+import React from 'react';
+import Dropdown from 'components/GUIKit/Dropdown';
+import _ from 'lodash';
+import PT from 'prop-types';
+
+import './style.scss';
+
+function DropdownExample({ size }) {
+ const options = [
+ { label: 'Afghanistan', selected: false },
+ { label: 'Albania', selected: true },
+ { label: 'Andorra', selected: false },
+ { label: 'Anguilla', selected: false },
+ { label: 'Belgium', selected: false },
+ { label: 'Brazil', selected: false },
+ ];
+
+ const values = [
+ {
+ key: 1,
+ options: _.cloneDeep(options.map(o => ({ ...o, selected: false }))),
+ label: 'Country',
+ required: false,
+ placeholder: 'Select country',
+ errorMsg: '',
+ sectionTitle: 'Empty',
+ },
+ {
+ key: 2,
+ options: _.cloneDeep(options),
+ label: 'Country',
+ required: false,
+ placeholder: '',
+ errorMsg: '',
+ sectionTitle: 'Filled',
+ },
+ {
+ key: 4,
+ options: _.cloneDeep(options),
+ label: 'Country',
+ required: false,
+ placeholder: '',
+ errorMsg: 'The country is wrong. Please check it again.',
+ sectionTitle: 'Error',
+ },
+ {
+ key: 3,
+ options: _.cloneDeep(options),
+ label: 'Country',
+ required: true,
+ placeholder: '',
+ errorMsg: '',
+ sectionTitle: 'Require',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+ {values.map(value => (
+
+ {value.sectionTitle}
+ onChange(changedOptions)}
+ options={value.options}
+ label={value.label}
+ required={value.required}
+ placeholder={value.placeholder}
+ errorMsg={value.errorMsg}
+ size={size}
+ />
+
+ ))}
+
+ );
+}
+
+DropdownExample.defaultProps = {
+ size: 'lg',
+};
+
+DropdownExample.propTypes = {
+ size: PT.oneOf(['xs', 'lg']),
+};
+
+export default DropdownExample;
diff --git a/src/shared/components/examples/GUIKit/Dropdown/style.scss b/src/shared/components/examples/GUIKit/Dropdown/style.scss
new file mode 100644
index 0000000000..fb6a4b5201
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Dropdown/style.scss
@@ -0,0 +1,33 @@
+@import '~styles/mixins';
+
+.container {
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+
+ :global {
+ .dropdownContainer {
+ flex-grow: 1;
+ width: 0;
+ }
+ }
+}
+
+.rowItem {
+ margin-top: 20px;
+ display: flex;
+ align-items: flex-start;
+ max-width: 520px;
+ flex-shrink: 0;
+ flex-wrap: wrap;
+}
+
+.sectionTitle {
+ margin-right: 24px;
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-left: 27px;
+ width: 50px;
+ flex-shrink: 0;
+ margin-top: 30px;
+}
diff --git a/src/shared/components/examples/GUIKit/DropdownTerms/index.jsx b/src/shared/components/examples/GUIKit/DropdownTerms/index.jsx
new file mode 100644
index 0000000000..110454360b
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/DropdownTerms/index.jsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import DropdownTerms from 'components/GUIKit/DropdownTerms';
+import _ from 'lodash';
+
+import './style.scss';
+
+export default function DropdownTermsExample() {
+ const options = [
+ { label: 'EJB', selected: false },
+ { label: 'Java', selected: true },
+ { label: 'Javascript', selected: false },
+ { label: 'MangoDB', selected: false },
+ { label: 'Oracle', selected: false },
+ { label: 'React', selected: false },
+ { label: 'Serverlet', selected: false },
+ { label: 'Web Services', selected: false },
+ { label: 'Zipkin', selected: false },
+ ];
+
+ const values = [
+ {
+ key: 1,
+ options: _.cloneDeep(options.map(o => ({ ...o, selected: false }))),
+ label: 'Tech Skills',
+ required: false,
+ placeholder: 'Tech Skills',
+ errorMsg: '',
+ sectionTitle: 'Empty',
+ addNewOptionPlaceholder: 'Type to add another skill...',
+ },
+ {
+ key: 2,
+ options: _.cloneDeep(options),
+ label: 'Tech Skills',
+ required: false,
+ placeholder: 'Tech Skills',
+ errorMsg: '',
+ sectionTitle: 'Filled',
+ addNewOptionPlaceholder: 'Type to add another skill...',
+ },
+ {
+ key: 4,
+ options: _.cloneDeep(options),
+ label: 'Tech Skills',
+ required: false,
+ placeholder: 'Tech Skills',
+ errorMsg: 'The skill is wrong. Please check it again.',
+ sectionTitle: 'Error',
+ addNewOptionPlaceholder: 'Type to add another skill...',
+ },
+ {
+ key: 5,
+ options: _.cloneDeep(options.map(o => ({ ...o, selected: false }))),
+ label: 'Tech Skills',
+ required: true,
+ placeholder: 'Tech Skills',
+ errorMsg: '',
+ sectionTitle: 'Require',
+ addNewOptionPlaceholder: 'Type to add another skill...',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+ {values.map(value => (
+
+ {value.sectionTitle}
+ onChange(changedOptions)}
+ terms={value.options}
+ label={value.label}
+ required={value.required}
+ placeholder={value.placeholder}
+ errorMsg={value.errorMsg}
+ addNewOptionPlaceholder={value.addNewOptionPlaceholder}
+ />
+
+ ))}
+
+ );
+}
diff --git a/src/shared/components/examples/GUIKit/DropdownTerms/style.scss b/src/shared/components/examples/GUIKit/DropdownTerms/style.scss
new file mode 100644
index 0000000000..cbda0d481c
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/DropdownTerms/style.scss
@@ -0,0 +1,33 @@
+@import '~styles/mixins';
+
+.container {
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+
+ :global {
+ .dropdownContainer {
+ flex-grow: 1;
+ width: 0;
+ }
+ }
+}
+
+.rowItem {
+ margin-top: 20px;
+ display: flex;
+ align-items: flex-start;
+ max-width: 970px;
+ flex-shrink: 0;
+ flex-wrap: wrap;
+}
+
+.sectionTitle {
+ margin-right: 24px;
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-left: 27px;
+ width: 50px;
+ flex-shrink: 0;
+ margin-top: 30px;
+}
diff --git a/src/shared/components/examples/GUIKit/RadioButton/index.jsx b/src/shared/components/examples/GUIKit/RadioButton/index.jsx
new file mode 100644
index 0000000000..06906851de
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/RadioButton/index.jsx
@@ -0,0 +1,83 @@
+import React from 'react';
+import RadioButton from 'components/GUIKit/RadioButton';
+
+import './style.scss';
+
+export default function RadioButtonExample() {
+ const values = [
+ {
+ key: 1,
+ options: [{ label: '', value: false }, { label: '', value: true }],
+ size: 'lg',
+ },
+ {
+ key: 3,
+ options: [{ label: '', value: false }, { label: '', value: true }],
+ size: 'sm',
+ },
+ {
+ key: 5,
+ options: [{ label: '', value: false }, { label: '', value: true }],
+ size: 'xs',
+ },
+ {
+ key: 7,
+ options: [{ label: 'Option 1', value: true }, { label: 'Option 2', value: false }],
+ size: 'lg',
+ },
+ {
+ key: 8,
+ options: [{ label: 'Option 1', value: true }, { label: 'Option 2', value: false }],
+ size: 'sm',
+ },
+ {
+ key: 9,
+ options: [{ label: 'Option 1', value: true }, { label: 'Option 2', value: false }],
+ size: 'xs',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+
+ onChange(options)}
+ size={values[0].size}
+ options={values[0].options}
+ />
+ onChange(options)}
+ size={values[1].size}
+ options={values[1].options}
+ />
+ onChange(options)}
+ size={values[2].size}
+ options={values[2].options}
+ />
+
+
+
Examples usage
+
+ onChange(options)}
+ size={values[3].size}
+ options={values[3].options}
+ />
+ onChange(options)}
+ size={values[4].size}
+ options={values[4].options}
+ />
+ onChange(options)}
+ size={values[5].size}
+ options={values[5].options}
+ />
+
+
+
+ );
+}
diff --git a/src/shared/components/examples/GUIKit/RadioButton/style.scss b/src/shared/components/examples/GUIKit/RadioButton/style.scss
new file mode 100644
index 0000000000..6775400632
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/RadioButton/style.scss
@@ -0,0 +1,59 @@
+@import '~styles/mixins';
+
+.container {
+ display: flex;
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+}
+
+.grid2,
+.grid1 {
+ display: flex;
+ flex-direction: column;
+}
+
+.grid2Label {
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-bottom: 20px;
+}
+
+.grid21 {
+ display: flex;
+ flex-wrap: wrap;
+
+ :global {
+ .radioButtonContainer {
+ margin-right: 60px;
+ height: 60px;
+ justify-content: space-around;
+ }
+ }
+}
+
+.grid1 {
+ margin-right: 100px;
+
+ :global {
+ .radioButtonContainer {
+ display: flex;
+ flex-direction: row;
+ margin-top: 20px;
+ width: 76px;
+ justify-content: space-between;
+
+ &:nth-child(2) {
+ width: 73px;
+ }
+
+ &:nth-child(3) {
+ width: 69px;
+ }
+ }
+ }
+}
+
+.grid2 {
+ margin-top: 20px;
+}
diff --git a/src/shared/components/examples/GUIKit/TextInput/index.jsx b/src/shared/components/examples/GUIKit/TextInput/index.jsx
new file mode 100644
index 0000000000..d8b932b049
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/TextInput/index.jsx
@@ -0,0 +1,77 @@
+import React from 'react';
+import TextInput from 'components/GUIKit/TextInput';
+import PT from 'prop-types';
+
+import './style.scss';
+
+function TextInputExample({ size }) {
+ const values = [
+ {
+ key: 2,
+ placeholder: 'Pick a username',
+ label: '',
+ value: '',
+ required: false,
+ errorMsg: '',
+ sectionTitle: 'Empty',
+ },
+ {
+ key: 3,
+ placeholder: 'Pick a username',
+ label: 'Username',
+ value: 'Adam Morehead',
+ required: false,
+ errorMsg: '',
+ sectionTitle: 'Filled',
+ },
+ {
+ key: 4,
+ placeholder: 'Pick a username',
+ label: 'Username',
+ value: 'Adam Morehead',
+ required: false,
+ errorMsg: 'This username is wrong. Please check it again.',
+ sectionTitle: 'Error',
+ },
+ {
+ key: 5,
+ placeholder: 'Pick a username',
+ label: 'Username',
+ value: 'Adam Morehead',
+ required: true,
+ errorMsg: '',
+ sectionTitle: 'Required',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+ {values.map(value => (
+
+ {value.sectionTitle}
+
+
+ ))}
+
+ );
+}
+
+TextInputExample.defaultProps = {
+ size: 'lg',
+};
+
+TextInputExample.propTypes = {
+ size: PT.oneOf(['xs', 'lg']),
+};
+
+export default TextInputExample;
diff --git a/src/shared/components/examples/GUIKit/TextInput/style.scss b/src/shared/components/examples/GUIKit/TextInput/style.scss
new file mode 100644
index 0000000000..6ee600c2c7
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/TextInput/style.scss
@@ -0,0 +1,31 @@
+@import '~styles/mixins';
+
+.container {
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+
+ :global {
+ .textInputContainer {
+ flex-grow: 1;
+ }
+ }
+}
+
+.rowItem {
+ margin-top: 20px;
+ display: flex;
+ align-items: flex-start;
+ max-width: 520px;
+ flex-shrink: 0;
+}
+
+.sectionTitle {
+ margin-right: 24px;
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-left: 27px;
+ width: 50px;
+ flex-shrink: 0;
+ margin-top: 30px;
+}
diff --git a/src/shared/components/examples/GUIKit/Textarea/index.jsx b/src/shared/components/examples/GUIKit/Textarea/index.jsx
new file mode 100644
index 0000000000..972b0f87c9
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Textarea/index.jsx
@@ -0,0 +1,65 @@
+import React from 'react';
+import Textarea from 'components/GUIKit/Textarea';
+
+import './style.scss';
+
+export default function TextareaExample() {
+ const values = [
+ {
+ key: 2,
+ placeholder: 'Briefly describe your request',
+ label: '',
+ value: '',
+ required: false,
+ errorMsg: '',
+ sectionTitle: 'Empty',
+ },
+ {
+ key: 3,
+ placeholder: 'Briefly describe your request',
+ label: 'Briefly describe your request',
+ value: 'Different members have differet reasons for joining Topcoder and that’s why I would like to propose one thing.',
+ required: false,
+ errorMsg: '',
+ sectionTitle: 'Filled',
+ },
+ {
+ key: 4,
+ placeholder: 'Briefly describe your request',
+ label: 'Briefly describe your request',
+ value: 'Different members have differet reasons for joining Topcoder and that’s why I would like to propose one thing.',
+ required: false,
+ errorMsg: 'Your request is wrong. Please check it again.',
+ sectionTitle: 'Error',
+ },
+ {
+ key: 5,
+ placeholder: 'Briefly describe your request',
+ label: 'Briefly describe your request',
+ value: 'Different members have differet reasons for joining Topcoder and that’s why I would like to propose one thing.',
+ required: true,
+ errorMsg: '',
+ sectionTitle: 'Require',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+ {values.map(value => (
+
+ {value.sectionTitle}
+
+
+ ))}
+
+ );
+}
diff --git a/src/shared/components/examples/GUIKit/Textarea/style.scss b/src/shared/components/examples/GUIKit/Textarea/style.scss
new file mode 100644
index 0000000000..f82e84ed50
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Textarea/style.scss
@@ -0,0 +1,33 @@
+@import '~styles/mixins';
+
+.container {
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+
+ :global {
+ .textareaContainer {
+ flex-grow: 1;
+ height: 176px;
+ }
+ }
+}
+
+.rowItem {
+ margin-top: 20px;
+ display: flex;
+ align-items: flex-start;
+ max-width: 520px;
+ min-height: 176px;
+ flex-shrink: 0;
+}
+
+.sectionTitle {
+ margin-right: 24px;
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-left: 27px;
+ width: 50px;
+ margin-top: 32px;
+ flex-shrink: 0;
+}
diff --git a/src/shared/components/examples/GUIKit/Toggles/index.jsx b/src/shared/components/examples/GUIKit/Toggles/index.jsx
new file mode 100644
index 0000000000..e043b124ef
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Toggles/index.jsx
@@ -0,0 +1,127 @@
+import React from 'react';
+import Toggles from 'components/GUIKit/Toggles';
+
+import './style.scss';
+
+export default function TogglesExample() {
+ const checkValues = [
+ {
+ label: '',
+ key: 1,
+ checked: false,
+ size: 'lg',
+ },
+ {
+ label: '',
+ key: 2,
+ checked: true,
+ size: 'lg',
+ },
+ {
+ label: '',
+ key: 3,
+ checked: false,
+ size: 'sm',
+ },
+ {
+ label: '',
+ key: 4,
+ checked: true,
+ size: 'sm',
+ },
+ {
+ label: '',
+ key: 5,
+ checked: false,
+ size: 'xs',
+ },
+ {
+ label: '',
+ key: 6,
+ checked: true,
+ size: 'xs',
+ },
+ {
+ label: 'Option 1',
+ key: 7,
+ checked: true,
+ size: 'lg',
+ },
+ {
+ label: 'Option 2',
+ key: 10,
+ checked: false,
+ size: 'lg',
+ },
+ ];
+
+ // eslint-disable-next-line no-console
+ const onChange = value => console.log('onChange', value);
+ return (
+
+
+
+
+ onChange(checked)}
+ size={checkValues[0].size}
+ checked={checkValues[0].checked}
+ />
+
+
onChange(checked)}
+ size={checkValues[1].size}
+ checked={checkValues[1].checked}
+ />
+
+
+
+ onChange(checked)}
+ size={checkValues[2].size}
+ checked={checkValues[2].checked}
+ />
+
+
onChange(checked)}
+ size={checkValues[3].size}
+ checked={checkValues[3].checked}
+ />
+
+
+
+ onChange(checked)}
+ size={checkValues[4].size}
+ checked={checkValues[4].checked}
+ />
+
+
onChange(checked)}
+ size={checkValues[5].size}
+ checked={checkValues[5].checked}
+ />
+
+
+
+
Examples usage
+
+ {checkValues[6].label}
+ onChange(checked)}
+ size={checkValues[6].size}
+ checked={checkValues[6].checked}
+ />
+
+
+ {checkValues[7].label}
+ onChange(checked)}
+ size={checkValues[7].size}
+ checked={checkValues[7].checked}
+ />
+
+
+
+ );
+}
diff --git a/src/shared/components/examples/GUIKit/Toggles/style.scss b/src/shared/components/examples/GUIKit/Toggles/style.scss
new file mode 100644
index 0000000000..00b21bdab6
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/Toggles/style.scss
@@ -0,0 +1,45 @@
+@import '~styles/mixins';
+
+.container {
+ display: flex;
+ font-family: Roboto, sans-serif;
+ flex-wrap: wrap;
+ flex-shrink: 0;
+}
+
+.grid1,
+.grid2 {
+ display: flex;
+ flex-direction: column;
+}
+
+.grid2 {
+ margin-left: 50px;
+ margin-top: 20px;
+}
+
+.grid11 {
+ display: flex;
+ margin-top: 20px;
+}
+
+.grid111 {
+ width: 70px;
+}
+
+.grid2Label {
+ font-size: 13px;
+ color: $tc-purple-110;
+ margin-bottom: 2px;
+}
+
+.grid21 {
+ display: flex;
+ align-items: center;
+ margin-top: 18px;
+}
+
+.grid21Label {
+ font-size: 14px;
+ margin-right: 10px;
+}
diff --git a/src/shared/components/examples/GUIKit/index.jsx b/src/shared/components/examples/GUIKit/index.jsx
new file mode 100644
index 0000000000..5836630ed6
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/index.jsx
@@ -0,0 +1,47 @@
+import React from 'react';
+import CheckboxExample from './Checkbox';
+import TextInputExample from './TextInput';
+import TextareaExample from './Textarea';
+import RadioButtonExample from './RadioButton';
+import DatepickerExample from './Datepicker';
+import DropdownTermsExample from './DropdownTerms';
+import TogglesExample from './Toggles';
+import DropdownExample from './Dropdown';
+
+import './style.scss';
+
+export default function GUIKit() {
+ return (
+
+
TEXTBOX
+
+
+ TEXTBOX SMALL
+
+
+ TEXTAREA
+
+
+ CHECKBOXES
+
+
+ RADIO BUTTONS
+
+
+ DATE PICKER
+
+
+ DROPDOWN SKILLS
+
+
+ TOGGLES
+
+
+ DROPDOWN
+
+
+ DROPDOWN SMALL
+
+
+ );
+}
diff --git a/src/shared/components/examples/GUIKit/style.scss b/src/shared/components/examples/GUIKit/style.scss
new file mode 100644
index 0000000000..2d9af1a8b5
--- /dev/null
+++ b/src/shared/components/examples/GUIKit/style.scss
@@ -0,0 +1,16 @@
+.container {
+ padding: 20px;
+ padding-bottom: 100px;
+
+ h1 {
+ font-size: 34px;
+ font-weight: 500 !important;
+ }
+}
+
+.sectionTitle {
+ text-transform: uppercase;
+ font-size: 20px;
+ font-weight: 600 !important;
+ margin-top: 50px;
+}
diff --git a/src/shared/containers/Gigs/RecruitCRMJobs.jsx b/src/shared/containers/Gigs/RecruitCRMJobs.jsx
index d09ba71afc..59b2973ba8 100644
--- a/src/shared/containers/Gigs/RecruitCRMJobs.jsx
+++ b/src/shared/containers/Gigs/RecruitCRMJobs.jsx
@@ -8,12 +8,20 @@ import LoadingIndicator from 'components/LoadingIndicator';
import SearchCombo from 'components/GUIKit/SearchCombo';
import Paginate from 'components/GUIKit/Paginate';
import JobListCard from 'components/GUIKit/JobListCard';
+import Dropdown from 'components/GUIKit/Dropdown';
import PT from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import './jobLisingStyles.scss';
const GIGS_PER_PAGE = 10;
+// Sort by dropdown
+const sortByOptions = [
+ { label: 'Latest Added Descending', selected: true },
+ { label: 'Latest Updated Descending', selected: false },
+];
+// Locations
+let locations = [];
class RecruitCRMJobsContainer extends React.Component {
constructor(props) {
@@ -23,11 +31,14 @@ class RecruitCRMJobsContainer extends React.Component {
term: '',
page: 0,
sortBy: 'created_on',
+ location: 'Any Location',
};
-
+ // binds
this.onSearch = this.onSearch.bind(this);
this.onPaginate = this.onPaginate.bind(this);
this.onFilter = this.onFilter.bind(this);
+ this.onLocation = this.onLocation.bind(this);
+ this.onSort = this.onSort.bind(this);
}
componentDidMount() {
@@ -69,6 +80,22 @@ class RecruitCRMJobsContainer extends React.Component {
});
}
+ onLocation(newLocation) {
+ const selected = _.find(newLocation, { selected: true });
+ this.onFilter({
+ location: selected.label,
+ page: 0,
+ });
+ }
+
+ onSort(newSort) {
+ const selected = _.find(newSort, { selected: true });
+ this.onFilter({
+ sortBy: selected.label === 'Latest Updated Descending' ? 'updated_on' : 'created_on',
+ page: 0,
+ });
+ }
+
render() {
const {
loading,
@@ -78,6 +105,7 @@ class RecruitCRMJobsContainer extends React.Component {
term,
page,
sortBy,
+ location,
} = this.state;
if (loading) {
@@ -85,9 +113,23 @@ class RecruitCRMJobsContainer extends React.Component {
}
let jobsToDisplay = jobs;
+ // build current locations dropdown based on all data
+ // and filter by selected location
+ jobsToDisplay = _.filter(jobs, (job) => {
+ // build dropdown
+ const found = _.find(locations, { label: job.country });
+ if (!found) {
+ locations.push({ label: job.country, selected: location === job.country });
+ }
+ // filter
+ if (location === 'Anywhere' || location === 'Any' || location === 'Any Location') return true;
+ return location.toLowerCase() === job.country.toLowerCase();
+ });
+ // sort location dropdown
+ locations = _.sortBy(locations, ['label']);
// Filter by term
if (term) {
- jobsToDisplay = _.filter(jobs, (job) => {
+ jobsToDisplay = _.filter(jobsToDisplay, (job) => {
// eslint-disable-next-line no-underscore-dangle
const _term = term.toLowerCase();
// name search
@@ -115,6 +157,8 @@ class RecruitCRMJobsContainer extends React.Component {
+
+
{
@@ -134,11 +178,12 @@ class RecruitCRMJobsContainer extends React.Component {
RecruitCRMJobsContainer.defaultProps = {
jobs: [],
+ loading: true,
};
RecruitCRMJobsContainer.propTypes = {
getJobs: PT.func.isRequired,
- loading: PT.bool.isRequired,
+ loading: PT.bool,
jobs: PT.arrayOf(PT.shape),
};
diff --git a/src/shared/containers/Gigs/jobLisingStyles.scss b/src/shared/containers/Gigs/jobLisingStyles.scss
index 4edc9b7c5b..1d909ee9c1 100644
--- a/src/shared/containers/Gigs/jobLisingStyles.scss
+++ b/src/shared/containers/Gigs/jobLisingStyles.scss
@@ -10,6 +10,28 @@
.filters {
display: flex;
+ align-items: flex-end;
+
+ @include xs-to-sm {
+ flex-direction: column;
+ }
+
+ > div {
+ margin-right: 30px;
+ flex: 1;
+
+ @include xs-to-sm {
+ margin-right: 0;
+ }
+
+ &:first-child {
+ flex: 3;
+ }
+
+ &:last-child {
+ margin-right: 0;
+ }
+ }
}
.jobs-list-container {
diff --git a/src/shared/containers/SubscribeMailChimpTag/index.jsx b/src/shared/containers/SubscribeMailChimpTag/index.jsx
new file mode 100644
index 0000000000..1b7117e348
--- /dev/null
+++ b/src/shared/containers/SubscribeMailChimpTag/index.jsx
@@ -0,0 +1,128 @@
+/**
+ * Genetic subscribe for MailChimp tags component
+ */
+import React from 'react';
+import PT from 'prop-types';
+import { isValidEmail } from 'utils/tc';
+import { Modal } from 'topcoder-react-ui-kit';
+import modalStyle from 'components/NewsletterSignupForMembers/modal.scss';
+import defaulTheme from './style.scss';
+
+/* Holds the base URL of Community App endpoints that proxy HTTP request to
+ * mailchimp APIs. */
+const PROXY_ENDPOINT = '/api/mailchimp';
+
+class SubscribeMailChimpTagContainer extends React.Component {
+ constructor(props) {
+ super(props);
+ this.state = {
+ error: '',
+ subsribed: false,
+ disabled: true,
+ inputVal: '',
+ };
+ this.onSubscribeClick = this.onSubscribeClick.bind(this);
+ this.onInputChange = this.onInputChange.bind(this);
+ }
+
+ onSubscribeClick() {
+ const { inputVal } = this.state;
+ const { listId, tags } = this.props;
+ const fetchUrl = `${PROXY_ENDPOINT}/${listId}/members/${inputVal}/tags`;
+ const data = {
+ email_address: inputVal,
+ status: 'subscribed',
+ tags: tags.map(t => ({ name: t, status: 'active' })),
+ };
+ const formData = JSON.stringify(data);
+ fetch(fetchUrl, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: formData,
+ }).then(result => result.json()).then((dataResponse) => {
+ if (dataResponse.status === 204) {
+ // regist success
+ return this.setState({
+ subsribed: true,
+ error: '',
+ });
+ }
+ if (dataResponse.status === 404) {
+ // new email register it for list and add tags
+ return fetch(`${PROXY_ENDPOINT}/${listId}/members`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ email_address: inputVal,
+ status: 'subscribed',
+ tags,
+ }),
+ })
+ .then(result => result.json()).then(() => {
+ this.setState({
+ subsribed: true,
+ error: '',
+ });
+ });
+ }
+ return this.setState({
+ subsribed: false,
+ error: `Error ${dataResponse.status} when assign to tags`,
+ });
+ })
+ .catch((e) => {
+ this.setState({
+ subsribed: false,
+ error: e.message,
+ });
+ });
+ }
+
+ onInputChange(event) {
+ this.setState({
+ inputVal: event.target.value,
+ disabled: !isValidEmail(event.target.value),
+ });
+ }
+
+ render() {
+ const {
+ disabled, inputVal, error, subsribed,
+ } = this.state;
+ return (
+
+ {
+ subsribed || error ? (
+
this.setState({
+ subsribed: false,
+ error: false,
+ inputVal: '',
+ disabled: true,
+ })}
+ >
+
+
{subsribed ? 'Congratulations!' : 'Ops :('}
+
{error || 'You are now subscribed.'}
+
+
+ ) : null
+ }
+
+
Subscribe
+
+ );
+ }
+}
+
+SubscribeMailChimpTagContainer.propTypes = {
+ listId: PT.string.isRequired,
+ tags: PT.arrayOf(PT.string).isRequired,
+};
+
+export default SubscribeMailChimpTagContainer;
diff --git a/src/shared/containers/SubscribeMailChimpTag/style.scss b/src/shared/containers/SubscribeMailChimpTag/style.scss
new file mode 100644
index 0000000000..9aa4059fba
--- /dev/null
+++ b/src/shared/containers/SubscribeMailChimpTag/style.scss
@@ -0,0 +1,55 @@
+.wrapper {
+ display: flex;
+
+ input {
+ background-color: #fff !important;
+ border: 1px solid #aaa !important;
+ border-radius: 6px !important;
+ margin-right: 8px;
+ color: #2a2a2a;
+ font-family: Roboto, sans-serif;
+ font-size: 14px;
+ line-height: 22px;
+ }
+
+ input::-webkit-input-placeholder,
+ input::placeholder {
+ color: #aaa !important;
+ font-family: Roboto, sans-serif !important;
+ font-size: 14px !important;
+ line-height: 40px !important;
+ text-transform: none;
+ }
+
+ input:focus {
+ box-shadow: none !important;
+ }
+
+ .button {
+ color: #229174;
+ font-family: Roboto, sans-serif;
+ font-size: 14px;
+ font-weight: bold;
+ letter-spacing: 0.8px;
+ line-height: 40px;
+ background-color: #fff;
+ border-radius: 20px;
+ border: none;
+ text-transform: uppercase;
+ max-height: 40px;
+ padding: 0 20px;
+ cursor: pointer;
+ outline: none;
+
+ &:disabled {
+ background-color: #e9e9e9;
+ color: #fff;
+ }
+
+ &:hover:not(:disabled) {
+ color: #229174;
+ background-color: #fff !important;
+ box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2);
+ }
+ }
+}
diff --git a/src/shared/routes/Examples/Examples.jsx b/src/shared/routes/Examples/Examples.jsx
index 04fb03a6fe..8c9bcd1403 100644
--- a/src/shared/routes/Examples/Examples.jsx
+++ b/src/shared/routes/Examples/Examples.jsx
@@ -30,6 +30,7 @@ import SearchBarExample from 'components/examples/SearchBar';
import TracksTreeExample from 'components/examples/TracksTree';
import TracksFilterExample from 'components/examples/TracksFilter';
import SearchPageFilterExample from 'components/examples/SearchPageFilter';
+import GUIKit from 'components/examples/GUIKit';
import {
Switch,
@@ -66,6 +67,7 @@ export default function Examples({
/>
+
diff --git a/src/shared/routes/Topcoder/styles.scss b/src/shared/routes/Topcoder/styles.scss
index 1af7c46852..517fe321bc 100644
--- a/src/shared/routes/Topcoder/styles.scss
+++ b/src/shared/routes/Topcoder/styles.scss
@@ -8,6 +8,6 @@
display: flex;
flex: 1 0 auto;
position: relative;
- z-index: 0;
+ z-index: 1;
}
}
diff --git a/src/shared/routes/index.jsx b/src/shared/routes/index.jsx
index be51690503..0f1b026e2b 100644
--- a/src/shared/routes/index.jsx
+++ b/src/shared/routes/index.jsx
@@ -95,6 +95,10 @@ function Routes({ communityId }) {
component={() =>
}
path="/community/(competitive-programming|data-science|design|development|qa)/how-to-compete"
/>
+
{
// extract the phases from `challenge.phases`,
// as `challenge.registrationStartDate` returned from API is not reliable
- const registrationPhase = find(challenge.phases, p => p.name === 'Registration');
- const submissionPhase = find(challenge.phases, p => p.name === 'Submission');
+ const registrationPhase = find(challenge.phases, p => p.name === 'Registration') || {};
+ const submissionPhase = find(challenge.phases, p => p.name === 'Submission') || {};
// registration phase exists
- if (registrationPhase) {
+ if (!isEmpty(registrationPhase)) {
return moment(phaseStartDate(registrationPhase));
}
// registration phase doesnt exist, This is possibly a F2F or TSK. Take submission phase
diff --git a/src/shared/utils/tc.js b/src/shared/utils/tc.js
index 92c93c9ca6..776729a973 100644
--- a/src/shared/utils/tc.js
+++ b/src/shared/utils/tc.js
@@ -287,4 +287,13 @@ export function formatDate(date, abbreviate, showDay) {
return `${month} ${y}`;
}
+/**
+ * Test if a string is valid email
+ * @param {String} email The string to test
+ */
+export function isValidEmail(email) {
+ const pattern = new RegExp(/^(("[\w-\s]+")|([\w-]+(?:\.[\w-]+)*)|("[\w-\s]+")([\w-]+(?:\.[\w-]+)*))(@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(@\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/i);
+ return pattern.test(email);
+}
+
export default undefined;
diff --git a/src/shared/utils/useWindowSize.js b/src/shared/utils/useWindowSize.js
new file mode 100644
index 0000000000..3c71376c9c
--- /dev/null
+++ b/src/shared/utils/useWindowSize.js
@@ -0,0 +1,29 @@
+import React from 'react';
+
+/**
+ * Get window size
+ */
+export default function useWindowSize() {
+ const isSSR = typeof window !== 'undefined';
+ const [windowSize, setWindowSize] = React.useState({
+ width: isSSR ? window.innerWidth : 1200,
+ height: isSSR ? window.innerHeight : 800,
+ });
+
+ function changeWindowSize() {
+ setWindowSize({
+ width: isSSR ? window.innerWidth : 1200,
+ height: isSSR ? window.innerHeight : 800,
+ });
+ }
+
+ React.useEffect(() => {
+ window.addEventListener('resize', changeWindowSize);
+
+ return () => {
+ window.removeEventListener('resize', changeWindowSize);
+ };
+ }, []);
+
+ return windowSize;
+}