diff --git a/.circleci/config.yml b/.circleci/config.yml
index 74ed1356ae..b8ec13cdea 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -261,7 +261,7 @@ workflows:
filters:
branches:
only:
- - develop
+ - feature-contentful
# Production builds are exectuted
# when PR is merged to the master
# Don't change anything in this configuration
diff --git a/__tests__/shared/components/GUIKit/Checkbox/__snapshots__/index.jsx.snap b/__tests__/shared/components/GUIKit/Checkbox/__snapshots__/index.jsx.snap
new file mode 100644
index 0000000000..94a5da518a
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/Checkbox/__snapshots__/index.jsx.snap
@@ -0,0 +1,25 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Default render 1`] = `
+
+
+
+
+
+
+`;
diff --git a/__tests__/shared/components/GUIKit/Checkbox/index.jsx b/__tests__/shared/components/GUIKit/Checkbox/index.jsx
new file mode 100644
index 0000000000..e693ad4989
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/Checkbox/index.jsx
@@ -0,0 +1,12 @@
+import React from 'react';
+import Renderer from 'react-test-renderer/shallow';
+
+import Checkbox from 'components/GUIKit/Checkbox';
+
+
+const rnd = new Renderer();
+
+it('Default render', () => {
+ rnd.render(( ));
+ expect(rnd.getRenderOutput()).toMatchSnapshot();
+});
diff --git a/__tests__/shared/components/GUIKit/Datepicker/__snapshots__/index.jsx.snap b/__tests__/shared/components/GUIKit/Datepicker/__snapshots__/index.jsx.snap
new file mode 100644
index 0000000000..6bf8ad76cf
--- /dev/null
+++ b/__tests__/shared/components/GUIKit/Datepicker/__snapshots__/index.jsx.snap
@@ -0,0 +1,118 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Default render 1`] = `
+
-
Thank you for checking out our latest gig at Topcoder. Gig work through us is simple and effective for those that would like traditional freelance work. To learn more about how Gigs work with us, go here .
At Topcoder, we pride ourselves in bringing our customers the very best candidates to help fill their needs. Want to improve your chances? You can do a few things:
@@ -137,14 +141,10 @@ export default function GigDetails(props) {
- Subscribe to our Gig notifications email . We’ll send you a weekly update on gigs available so you don’t miss a beat.
+ Let us know you’re here! Check in on our
Gig Work forum and tell us you’re looking for a gig. It’s great visibility for the Gig team.
- Let us know you’re here! Check in on our
Gig Work forum and tell us you’re looking for a gig. It’s great visibility for the Gig team.
-
-
-
Check out our Topcoder challenges and participate. Challenges showing your technology skills make you a “qualified” candidate so we know you’re good. The proof is in the pudding!
diff --git a/src/shared/components/Gigs/style.scss b/src/shared/components/Gigs/style.scss
index 9859cdc418..74c0cf3502 100644
--- a/src/shared/components/Gigs/style.scss
+++ b/src/shared/components/Gigs/style.scss
@@ -101,6 +101,7 @@
.shareButtons {
display: flex;
align-items: center;
+ margin-bottom: 12px;
a {
margin-right: 5px;
@@ -111,11 +112,27 @@
}
}
+ .subscribe-area {
+ background-image: linear-gradient(135.29deg, #2c95d7 0%, #06d6a0 100%);
+ border-radius: 10px;
+ padding: 25px 32px 30px 20px;
+
+ h6 {
+ margin: 0 0 6px;
+ color: #fff;
+ }
+
+ p {
+ color: #fff;
+ margin-bottom: 15px;
+ }
+ }
+
.info-area {
background-color: #f4f4f4;
padding: 20px;
border-radius: 10px;
- margin-top: 18px;
+ margin-top: 20px;
/* stylelint-disable */
p,
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/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;
+}