diff --git a/.circleci/config.yml b/.circleci/config.yml index 6f3b9fc80c..e3e323f801 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -245,7 +245,7 @@ workflows: filters: branches: only: - - develop + - gig-application # This is beta env for production soft releases - "build-prod-beta": context : org-global diff --git a/__tests__/shared/components/GUIKit/Checkbox/__snapshots__/index.jsx.snap b/__tests__/shared/components/GUIKit/Checkbox/__snapshots__/index.jsx.snap index 94a5da518a..2ca9d09aa5 100644 --- a/__tests__/shared/components/GUIKit/Checkbox/__snapshots__/index.jsx.snap +++ b/__tests__/shared/components/GUIKit/Checkbox/__snapshots__/index.jsx.snap @@ -12,13 +12,10 @@ exports[`Default render 1`] = `
-
diff --git a/__tests__/shared/components/GUIKit/Datepicker/__snapshots__/index.jsx.snap b/__tests__/shared/components/GUIKit/Datepicker/__snapshots__/index.jsx.snap index 6bf8ad76cf..8ec048cd49 100644 --- a/__tests__/shared/components/GUIKit/Datepicker/__snapshots__/index.jsx.snap +++ b/__tests__/shared/components/GUIKit/Datepicker/__snapshots__/index.jsx.snap @@ -2,7 +2,7 @@ exports[`Default render 1`] = `
} date={null} - daySize={53} + daySize={47} disableScroll={false} disabled={false} displayFormat="MMM DD, YYYY" diff --git a/__tests__/shared/components/GUIKit/RadioButton/__snapshots__/index.jsx.snap b/__tests__/shared/components/GUIKit/RadioButton/__snapshots__/index.jsx.snap index 29192cd7c7..f7455f2db2 100644 --- a/__tests__/shared/components/GUIKit/RadioButton/__snapshots__/index.jsx.snap +++ b/__tests__/shared/components/GUIKit/RadioButton/__snapshots__/index.jsx.snap @@ -1,29 +1,31 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Default render 1`] = ` -
+
- - - radio - + className="src-shared-components-GUIKit-RadioButton-___style__label___MCxnk" + > + radio + +
-
+ `; diff --git a/package-lock.json b/package-lock.json index 9835410199..3c35321344 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1934,6 +1934,11 @@ "lodash.throttle": "^4.0.1" } }, + "append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" + }, "append-transform": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", @@ -2235,12 +2240,9 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" }, "attr-accept": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.3.tgz", - "integrity": "sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==", - "requires": { - "core-js": "^2.5.0" - } + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==" }, "auth0-js": { "version": "9.13.2", @@ -3911,6 +3913,38 @@ "safe-json-stringify": "~1" } }, + "busboy": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.14.tgz", + "integrity": "sha1-bCpiLvz0fFe7vh4qnDetNseSVFM=", + "requires": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", @@ -4750,7 +4784,6 @@ "version": "1.6.2", "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, "requires": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -6716,6 +6749,38 @@ "defined": "^1.0.0" } }, + "dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "requires": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + } + } + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -8312,6 +8377,11 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "fast-safe-stringify": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + }, "fastparse": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", @@ -8396,6 +8466,21 @@ "schema-utils": "^0.4.5" } }, + "file-selector": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.1.13.tgz", + "integrity": "sha512-T2efCBY6Ps+jLIWdNQsmzt/UnAjKOEAlsZVdnQztg/BtAZGNL4uX1Jet9cMM8gify/x4CSudreji2HssGBNVIQ==", + "requires": { + "tslib": "^2.0.1" + }, + "dependencies": { + "tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==" + } + } + }, "file-set": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/file-set/-/file-set-2.0.1.tgz", @@ -8999,12 +9084,12 @@ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", - "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", "requires": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", + "combined-stream": "^1.0.8", "mime-types": "^2.1.12" } }, @@ -14482,6 +14567,21 @@ "isarray": "^1.0.0" } }, + "multer": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.2.tgz", + "integrity": "sha512-xY8pX7V+ybyUpbYMxtjM9KAiD9ixtg5/JkeKUTD6xilfDv0vzzOFcCp4Ljb1UU3tSOM3VTZtKo63OmzOrGi3Cg==", + "requires": { + "append-field": "^1.0.0", + "busboy": "^0.2.11", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.1", + "object-assign": "^4.1.1", + "on-finished": "^2.3.0", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + } + }, "mute-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", @@ -18088,12 +18188,13 @@ } }, "react-dropzone": { - "version": "3.13.4", - "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-3.13.4.tgz", - "integrity": "sha1-hNomgVxAM5aRxJtFRMLvehaRLMw=", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-11.2.0.tgz", + "integrity": "sha512-S/qaXQHCCg7MVlcrhqd05MLC6DupITLUB0CFn3iCLs6OTjzxdGDF1WTktTe5Jyq8jZdxYfMHNUZOHL0mg+K0Dw==", "requires": { - "attr-accept": "^1.0.3", - "prop-types": "^15.5.7" + "attr-accept": "^2.0.0", + "file-selector": "^0.1.12", + "prop-types": "^15.7.2" } }, "react-fast-compare": { @@ -20392,7 +20493,8 @@ "semver-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=" + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true }, "semver-regex": { "version": "2.0.0", @@ -21145,6 +21247,11 @@ "integrity": "sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==", "dev": true }, + "streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" + }, "strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", @@ -22099,6 +22206,16 @@ "ms": "^2.1.1" } }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -22896,12 +23013,10 @@ "filestack-js": "^1.13.2", "formsy-react": "^0.19.5", "isomorphic-fetch": "^2.2.1", - "libphonenumber-js": "1.4.6", "lodash": "^4.0.0", "material-ui": "^0.20.2", "moment": "^2.11.2", "prop-types": "^15.7.2", - "rc-slider": "8.6.4", "react": "^15.3.1", "react-addons-pure-render-mixin": "^15.3.1", "react-addons-update": "^15.3.1", @@ -22918,7 +23033,6 @@ "react-textarea-autosize": "^5.2.1", "react-transition-group": "^2.2.1", "redux-thunk": "^2.1.0", - "tc-ui": "git+https://github.com/appirio-tech/tc-ui.git#feature/connectv2", "uncontrollable": "^4.0.1" }, "dependencies": { @@ -22965,16 +23079,6 @@ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz", "integrity": "sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs=" }, - "libphonenumber-js": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.4.6.tgz", - "integrity": "sha512-TD1DyhPjVfNNiIxhwsooCO5j9L6JB60Qd+rlIEItgw8RKEqezu8Tva3V/4wrBiYMnBOHkp3uyzAe/PT9omyUdw==", - "requires": { - "minimist": "^1.2.0", - "semver-compare": "^1.0.0", - "xml2js": "^0.4.17" - } - }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -22983,11 +23087,6 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, "moment": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", @@ -23016,20 +23115,6 @@ "strict-uri-encode": "^1.0.0" } }, - "rc-slider": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-8.6.4.tgz", - "integrity": "sha512-CV2i2Ww6ib0EjFuBKvgjw3PgT6QwvWKC93iEpqPtrztZrx5wO9Iw//AUri4KHRqptW13AuBvFdEHovqLi6XFTw==", - "requires": { - "babel-runtime": "6.x", - "classnames": "^2.2.5", - "prop-types": "^15.5.4", - "rc-tooltip": "^3.7.0", - "rc-util": "^4.0.4", - "shallowequal": "^1.0.1", - "warning": "^3.0.0" - } - }, "react-router": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/react-router/-/react-router-2.8.1.tgz", @@ -23042,6 +23127,40 @@ "warning": "^3.0.0" } }, + "tc-ui": { + "version": "git+https://github.com/appirio-tech/tc-ui.git#e577a0e704136f1e9ecce92ce4c0626aab932691", + "from": "git+https://github.com/appirio-tech/tc-ui.git#e577a0e704136f1e9ecce92ce4c0626aab932691", + "requires": { + "classnames": "^2.2.3", + "lodash": "^4.0.0", + "moment": "^2.11.2", + "node-neat": "~1.7.1-beta1", + "react": "^0.14.7", + "react-datetime": "^2.0.2", + "react-dom": "^0.14.7", + "react-dropzone": "^3.3.2", + "react-redux": "^4.2.1", + "react-router": "^2.0.0-rc6", + "react-select": "^0.9.1", + "redux": "^3.3.1" + }, + "dependencies": { + "react": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/react/-/react-0.14.9.tgz", + "integrity": "sha1-kRCmSXxJ1EuhwO3TF67CnC4NkdE=", + "requires": { + "envify": "^3.0.0", + "fbjs": "^0.6.1" + } + }, + "react-dom": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-0.14.9.tgz", + "integrity": "sha1-BQZKPc8PsYgKOyv8nVjFXY2fYpM=" + } + } + }, "warning": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", @@ -23049,20 +23168,6 @@ "requires": { "loose-envify": "^1.0.0" } - }, - "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - } - }, - "xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" } } }, @@ -31603,111 +31708,6 @@ "inherits": "2" } }, - "tc-ui": { - "version": "git+https://github.com/appirio-tech/tc-ui.git#e577a0e704136f1e9ecce92ce4c0626aab932691", - "from": "git+https://github.com/appirio-tech/tc-ui.git#feature/connectv2", - "requires": { - "classnames": "^2.2.3", - "lodash": "^4.0.0", - "moment": "^2.11.2", - "node-neat": "~1.7.1-beta1", - "react": "^0.14.7", - "react-datetime": "^2.0.2", - "react-dom": "^0.14.7", - "react-dropzone": "^3.3.2", - "react-redux": "^4.2.1", - "react-router": "^2.0.0-rc6", - "react-select": "^0.9.1", - "redux": "^3.3.1" - }, - "dependencies": { - "fbjs": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.6.1.tgz", - "integrity": "sha1-lja3cF9bqWhNRLcveDISVK/IYPc=", - "requires": { - "core-js": "^1.0.0", - "loose-envify": "^1.0.0", - "promise": "^7.0.3", - "ua-parser-js": "^0.7.9", - "whatwg-fetch": "^0.9.0" - } - }, - "history": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/history/-/history-2.1.2.tgz", - "integrity": "sha1-SqLeiXoOSGfkU5hDvm7Nsphr/ew=", - "requires": { - "deep-equal": "^1.0.0", - "invariant": "^2.0.0", - "query-string": "^3.0.0", - "warning": "^2.0.0" - }, - "dependencies": { - "warning": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-2.1.0.tgz", - "integrity": "sha1-ISINnGOvx3qMkhEeARr3Bc4MaQE=", - "requires": { - "loose-envify": "^1.0.0" - } - } - } - }, - "hoist-non-react-statics": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz", - "integrity": "sha1-qkSM8JhtVcxAdzsXF0t90GbLfPs=" - }, - "moment": { - "version": "2.27.0", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz", - "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ==" - }, - "query-string": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-3.0.3.tgz", - "integrity": "sha1-ri4UtNBQcdTpuetIc8NbDc1C5jg=", - "requires": { - "strict-uri-encode": "^1.0.0" - } - }, - "react": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/react/-/react-0.14.9.tgz", - "integrity": "sha1-kRCmSXxJ1EuhwO3TF67CnC4NkdE=", - "requires": { - "envify": "^3.0.0", - "fbjs": "^0.6.1" - } - }, - "react-dom": { - "version": "0.14.9", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-0.14.9.tgz", - "integrity": "sha1-BQZKPc8PsYgKOyv8nVjFXY2fYpM=" - }, - "react-router": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-2.8.1.tgz", - "integrity": "sha1-c+lJH2zrMW0Pd5gpCBhj43juTtc=", - "requires": { - "history": "^2.1.2", - "hoist-non-react-statics": "^1.2.0", - "invariant": "^2.2.1", - "loose-envify": "^1.2.0", - "warning": "^3.0.0" - } - }, - "warning": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz", - "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=", - "requires": { - "loose-envify": "^1.0.0" - } - } - } - }, "tcomb": { "version": "3.2.29", "resolved": "https://registry.npmjs.org/tcomb/-/tcomb-3.2.29.tgz", @@ -32553,6 +32553,21 @@ "redux": "^3.3.1" }, "dependencies": { + "attr-accept": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-1.1.3.tgz", + "integrity": "sha512-iT40nudw8zmCweivz6j58g+RT33I4KbaIvRUhjNmDwO2WmsQUxFEZZYZ5w3vXe5x5MX9D7mfvA/XaLOZYFR9EQ==", + "requires": { + "core-js": "^2.5.0" + }, + "dependencies": { + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + } + } + }, "core-js": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", @@ -32621,6 +32636,15 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-0.14.9.tgz", "integrity": "sha1-BQZKPc8PsYgKOyv8nVjFXY2fYpM=" }, + "react-dropzone": { + "version": "3.13.4", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-3.13.4.tgz", + "integrity": "sha1-hNomgVxAM5aRxJtFRMLvehaRLMw=", + "requires": { + "attr-accept": "^1.0.3", + "prop-types": "^15.5.7" + } + }, "react-input-autosize": { "version": "0.6.13", "resolved": "https://registry.npmjs.org/react-input-autosize/-/react-input-autosize-0.6.13.tgz", @@ -33204,9 +33228,9 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, "topcoder-react-lib": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/topcoder-react-lib/-/topcoder-react-lib-1.0.3.tgz", - "integrity": "sha512-cb4QLW2m3CqzJHWkfbx5TM/rV1bpJXk3FqetNIyKETipebkXtbL83mxtkaV3IEDYrpeKmfzDwjmbxwBh8Gopkw==", + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/topcoder-react-lib/-/topcoder-react-lib-1.0.5.tgz", + "integrity": "sha512-vit+BVaU5SbjwTzLDAqFxE2P539+huV6QspsV3Ud5fL12HQqu3cpJg8iZLmEcCPSXonocVyhtvqdrSgqF1oB4Q==", "requires": { "auth0-js": "^6.8.4", "config": "^3.2.0", @@ -33265,6 +33289,14 @@ "json5": "^2.1.1" } }, + "debug": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", + "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", + "requires": { + "ms": "2.1.2" + } + }, "follow-redirects": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.7.tgz", @@ -33272,6 +33304,31 @@ "requires": { "debug": "^2.2.0", "stream-consume": "^0.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "form-data": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.0.tgz", + "integrity": "sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" } }, "hoist-non-react-statics": { @@ -33290,6 +33347,16 @@ "minimist": "^1.2.5" } }, + "mime": { + "version": "2.4.6", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz", + "integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "object-keys": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", @@ -33308,11 +33375,44 @@ "react-is": "^16.8.2" } }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==" + }, "serialize-javascript": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.9.1.tgz", "integrity": "sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==" }, + "superagent": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-5.3.1.tgz", + "integrity": "sha512-wjJ/MoTid2/RuGCOFtlacyGNxN9QLMgcpYLDQlWFIhhdJ93kNscFonGvrpAHSCVjRVj++DGCglocF7Aej1KHvQ==", + "requires": { + "component-emitter": "^1.3.0", + "cookiejar": "^2.1.2", + "debug": "^4.1.1", + "fast-safe-stringify": "^2.0.7", + "form-data": "^3.0.0", + "formidable": "^1.2.2", + "methods": "^1.1.2", + "mime": "^2.4.6", + "qs": "^6.9.4", + "readable-stream": "^3.6.0", + "semver": "^7.3.2" + } + }, "tc-core-library-js": { "version": "github:appirio-tech/tc-core-library-js#d16413db30b1eed21c0cf426e185bedb2329ddab", "from": "github:appirio-tech/tc-core-library-js#v2.6", @@ -33329,15 +33429,15 @@ }, "dependencies": { "auth0-js": { - "version": "9.13.4", - "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-9.13.4.tgz", - "integrity": "sha512-G7wXTtEUe8OG5UMdcFPoS47odorEZ3WerNyWLLhoGlLqYcPgv0t+B0ECHv/rVLULbpctbSBrRFFYa43/bJV4+Q==", + "version": "9.14.0", + "resolved": "https://registry.npmjs.org/auth0-js/-/auth0-js-9.14.0.tgz", + "integrity": "sha512-40gIBUejmYAYse06ck6sxdNO0KU0pX+KDIQsWAkcyFtI0HU6dY5aeHxZfVYkYjtbArKr5s13LuZFdKrUiGyCqQ==", "requires": { "base64-js": "^1.3.0", "idtoken-verifier": "^2.0.3", "js-cookie": "^2.2.0", "qs": "^6.7.0", - "superagent": "^3.8.3", + "superagent": "^5.3.1", "url-join": "^4.0.1", "winchan": "^0.2.2" } @@ -33436,9 +33536,9 @@ } }, "topcoder-react-ui-kit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/topcoder-react-ui-kit/-/topcoder-react-ui-kit-2.0.0.tgz", - "integrity": "sha512-9Ph8fRzRjVVB0VH13s8/N4+/ZWPLflrnW7D0fmS+oeDwnQe6k5wMknyFcBCI/pqWHr/11nhclXX7np3LdyWSwA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/topcoder-react-ui-kit/-/topcoder-react-ui-kit-2.0.1.tgz", + "integrity": "sha512-pl8tysSZYHDSbWn8srLZxV+8lK9f32ya8N+yt5C2XF2PKak30Qnqb3PCDhBO1fKmFF90p4ohJRsCi0IxLRyH/A==", "requires": { "prop-types": "^15.6.2", "react": "^16.4.1", @@ -33624,8 +33724,7 @@ "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" }, "typical": { "version": "4.0.0", diff --git a/package.json b/package.json index 5649814629..f89cbaede7 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "filestack-react": "^2.0.0", "flag-icon-css": "^3.3.0", "focus-trap-react": "^6.0.0", + "form-data": "^3.0.0", "helmet": "^3.12.1", "highlight.js": "^9.18.1", "html-to-text": "^5.1.1", @@ -81,6 +82,7 @@ "moment-timezone": "^0.5.21", "money": "^0.2.0", "morgan": "^1.9.0", + "multer": "^1.4.2", "navigation-component": "topcoder-platform/navigation-component#develop", "node-forge": "^0.7.5", "nuka-carousel": "^4.5.3", @@ -99,6 +101,7 @@ "react-dates": "^18.2.2", "react-dom": "^16.4.1", "react-dotdotdot": "^1.3.1", + "react-dropzone": "^11.2.0", "react-ga": "^2.7.0", "react-helmet": "^5.2.0", "react-html-parser": "^2.0.2", diff --git a/src/assets/images/back-arrow-gig-apply.svg b/src/assets/images/back-arrow-gig-apply.svg new file mode 100644 index 0000000000..ab3b9f0bbb --- /dev/null +++ b/src/assets/images/back-arrow-gig-apply.svg @@ -0,0 +1,17 @@ + + + + +BD4BD0F4-C75C-4897-A58C-B092051E2535 +Created with sketchtool. + + + + + + + + diff --git a/src/assets/images/big-checkmark.png b/src/assets/images/big-checkmark.png new file mode 100644 index 0000000000..8bd83bc41e Binary files /dev/null and b/src/assets/images/big-checkmark.png differ diff --git a/src/assets/images/sad-face-icon.svg b/src/assets/images/sad-face-icon.svg new file mode 100644 index 0000000000..b9b685c036 --- /dev/null +++ b/src/assets/images/sad-face-icon.svg @@ -0,0 +1,20 @@ + + + + 838C3DF4-2CF1-42A3-BC49-21142AF2003C@2x + Created with sketchtool. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/server/routes/recruitCRM.js b/src/server/routes/recruitCRM.js index 5047dcdb0d..34c1831539 100644 --- a/src/server/routes/recruitCRM.js +++ b/src/server/routes/recruitCRM.js @@ -6,7 +6,15 @@ import express from 'express'; import RecruitCRMService from '../services/recruitCRM'; const cors = require('cors'); - +const multer = require('multer'); + +const storage = multer.memoryStorage(); +const upload = multer({ + storage, + limits: { + fileSize: 8000000, + }, +}); const routes = express.Router(); // Enables CORS on those routes according config above @@ -15,9 +23,9 @@ routes.use(cors()); routes.options('*', cors()); routes.get('/jobs', (req, res, next) => new RecruitCRMService().getAllJobs(req, res, next)); - routes.get('/jobs/search', (req, res, next) => new RecruitCRMService().getJobs(req, res, next)); - routes.get('/jobs/:id', (req, res, next) => new RecruitCRMService().getJob(req, res, next)); +routes.post('/jobs/:id/apply', upload.single('resume'), (req, res, next) => new RecruitCRMService().applyForJob(req, res, next)); +routes.get('/candidates/search', (req, res, next) => new RecruitCRMService().searchCandidates(req, res, next)); export default routes; diff --git a/src/server/services/recruitCRM.js b/src/server/services/recruitCRM.js index b8659ae824..f5375cae5b 100644 --- a/src/server/services/recruitCRM.js +++ b/src/server/services/recruitCRM.js @@ -6,6 +6,8 @@ import config from 'config'; import qs from 'qs'; import _ from 'lodash'; +const FormData = require('form-data'); + /** * Auxiliary class that handles communication with recruitCRM */ @@ -141,4 +143,155 @@ export default class RecruitCRMService { return next(err); } } + + /** + * Search for candidate by email endpoint. + * @return {Promise} + * @param {Object} the request. + */ + async searchCandidates(req, res, next) { + try { + const response = await fetch(`${this.private.baseUrl}/v1/candidates/search?${qs.stringify(req.query)}`, { + method: 'GET', + headers: { + 'Content-Type': req.headers['content-type'], + Authorization: this.private.authorization, + }, + }); + if (response.status === 429) { + await new Promise(resolve => setTimeout(resolve, 30000)); // wait 30sec + return this.searchCandidates(req, res, next); + } + if (response.status >= 400) { + return res.send({ + error: true, + status: response.status, + url: `${this.private.baseUrl}/v1/candidates/search?${qs.stringify(req.query)}`, + }); + } + const data = await response.json(); + return res.send(data); + } catch (err) { + return next(err); + } + } + + /** + * Apply for candidate for job endpoint. + * @return {Promise} + * @param {Object} the request. + */ + async applyForJob(req, res, next) { + const { id } = req.params; + const { body, file } = req; + const form = JSON.parse(body.form); + const fileData = new FormData(); + fileData.append('resume', file.buffer, file.originalname); + let candidateSlug; + try { + // Check if candidate exsits in the system? + const candidateResponse = await fetch(`${this.private.baseUrl}/v1/candidates/search?email=${form.email}`, { + method: 'GET', + headers: { + 'Content-Type': req.headers['content-type'], + Authorization: this.private.authorization, + }, + }); + if (candidateResponse.status >= 400) { + return res.send({ + error: true, + status: candidateResponse.status, + url: `${this.private.baseUrl}/v1/candidates/search?email=${form.email}`, + errObj: await candidateResponse.json(), + }); + } + let candidateData = await candidateResponse.json(); + if (candidateData.data) { + // Candidate exists we will update profile fields + // otherwise we create it + candidateSlug = candidateData.data[0].slug; + const fieldIndexProfile = _.findIndex( + candidateData.data[0].custom_fields, { field_id: 14 }, + ); + const fieldIndexForm = _.findIndex(form.custom_fields, { field_id: 14 }); + if (fieldIndexProfile !== -1 && fieldIndexForm !== -1) { + form.custom_fields[fieldIndexForm].value += `;${candidateData.data[0].custom_fields[fieldIndexProfile].value}`; + if (form.custom_fields[fieldIndexForm].value.length > 2000) { + form.custom_fields[fieldIndexForm].value = form.custom_fields[ + fieldIndexForm].value.slice(0, 2000); + } + } + } + // Create/update candidate profile + const workCandidateResponse = await fetch(`${this.private.baseUrl}/v1/candidates${candidateSlug ? `/${candidateSlug}` : ''}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: this.private.authorization, + }, + body: JSON.stringify(form), + }); + if (workCandidateResponse.status >= 400) { + return res.send({ + error: true, + status: workCandidateResponse.status, + url: `${this.private.baseUrl}/v1/candidates${candidateSlug ? `/${candidateSlug}` : ''}`, + form, + errObj: await workCandidateResponse.json(), + }); + } + candidateData = await workCandidateResponse.json(); + // Attach resume to candidate + const formHeaders = fileData.getHeaders(); + const fileCandidateResponse = await fetch(`${this.private.baseUrl}/v1/candidates/${candidateData.slug}`, { + method: 'POST', + headers: { + Authorization: this.private.authorization, + ...formHeaders, + }, + body: fileData, + }); + if (fileCandidateResponse.status >= 400) { + return res.send({ + error: true, + status: fileCandidateResponse.status, + url: `${this.private.baseUrl}/v1/candidates/${candidateData.slug}`, + form, + fileData, + file, + formHeaders, + errObj: await fileCandidateResponse.json(), + }); + } + candidateData = await fileCandidateResponse.json(); + // Candidate ready to apply for job + const applyResponse = await fetch(`${this.private.baseUrl}/v1/candidates/${candidateData.slug}/assign?job_slug=${id}`, { + method: 'POST', + headers: { + 'Content-Type': req.headers['content-type'], + Authorization: this.private.authorization, + }, + }); + if (applyResponse.status >= 400) { + const errObj = await applyResponse.json(); + if (errObj.errorCode === 422 && errObj.errorMessage === 'Candidate is already assigned to this job') { + return res.send({ + success: true, + }); + } + return res.send({ + error: true, + status: applyResponse.status, + url: `${this.private.baseUrl}/v1/candidates/${candidateData.slug}/assign?job_slug=${id}`, + form, + candidateData, + errObj, + }); + } + const data = await applyResponse.json(); + return res.send(data); + } catch (err) { + return next(err); + } + } } diff --git a/src/shared/actions/recruitCRM.js b/src/shared/actions/recruitCRM.js index 670b71878a..544f9deb76 100644 --- a/src/shared/actions/recruitCRM.js +++ b/src/shared/actions/recruitCRM.js @@ -1,5 +1,6 @@ import { redux } from 'topcoder-react-utils'; import Service from 'services/recruitCRM'; +import _ from 'lodash'; /** * Jobs page fetch init @@ -40,11 +41,96 @@ async function getJobDone(id) { }; } +/** + * Apply for Job init + */ +function applyForJobInit(job, payload) { + return { id: job.slug, payload }; +} + +/** + * Helper utitlity + * @param {object} joib The job object + * @param {object} payload The apply form payload + */ +function normalizeRecruitPayload(job, payload) { + const perJob = [ + `${job.name} ->`, + `Pay Expectation: ${payload.payExpectation}`, + `Date Available: ${new Date(payload.availFrom).toDateString()}`, + `Heard About Gig: ${payload.reffereal}`, + `Why fit: ${payload.whyFit}`, + `Availability Per Week: ${payload.timeAvailability.filter(s => s.checked).map(s => s.label).join(',')}`, + `Able to work during timezone? ${payload.timezoneConfirm.filter(s => s.value).map(s => s.label).join(',')}`, + `Am I ok to work the duration? ${payload.durationConfirm.filter(s => s.value).map(s => s.label).join(',')}`, + `Notes: ${payload.notes}`, + ]; + return { + last_name: payload.lname, + first_name: payload.fname, + email: payload.email, + contact_number: payload.phone, + city: payload.city, + locality: _.find(payload.country, { selected: true }).label, + available_from: payload.availFrom, + salary_expectation: payload.payExpectation, + skill: payload.skills.filter(s => s.selected).map(s => s.label).join(','), + custom_fields: [ + { + field_id: 13, + value: payload.reffereal || '', + }, + { + field_id: 1, + value: payload.tcProfileLink || (payload.handle ? `topcoder.com/members/${payload.handle}` : ''), + }, + { + field_id: 2, + value: payload.handle || '', + }, + { + field_id: 3, + value: payload.whyFit || '', + }, + { + field_id: 14, + value: perJob.join(','), + }, + ], + resume: payload.fileCV, + }; +} + +/** + * Apply for Job done + */ +async function applyForJobDone(job, payload) { + const ss = new Service(); + try { + const res = await ss.applyForJob(job.slug, normalizeRecruitPayload(job, payload)); + + return { + id: job.slug, + data: res, + }; + } catch (error) { + return { + id: job.slug, + data: { + error: true, + errorObj: error, + }, + }; + } +} + export default redux.createActions({ RECRUIT: { GET_JOBS_INIT: getJobsInit, GET_JOBS_DONE: getJobsDone, GET_JOB_INIT: getJobInit, GET_JOB_DONE: getJobDone, + APPLY_FOR_JOB_INIT: applyForJobInit, + APPLY_FOR_JOB_DONE: applyForJobDone, }, }); diff --git a/src/shared/components/Contentful/Modal/index.jsx b/src/shared/components/Contentful/Modal/index.jsx index 196dc72074..55fcd996c2 100644 --- a/src/shared/components/Contentful/Modal/index.jsx +++ b/src/shared/components/Contentful/Modal/index.jsx @@ -9,12 +9,14 @@ import LoadingIndicator from 'components/LoadingIndicator'; import Banner from 'components/Contentful/Banner'; import ContentBlock from 'components/Contentful/ContentBlock'; import Viewport from 'components/Contentful/Viewport'; -import { Modal } from 'topcoder-react-ui-kit'; +import { Modal, PrimaryButton } from 'topcoder-react-ui-kit'; import { errors } from 'topcoder-react-lib'; import { themr } from 'react-css-super-themr'; import classnames from 'classnames'; +import tc from 'components/buttons/themed/tc.scss'; +import { fixStyle } from 'utils/contentful'; -import defaultModalTheme from 'components/Leaderboard/ChallengeHistoryModal/styles.scss'; +import defaultModalTheme from './modal-styles.scss'; import defaultStyle from './style.scss'; const { fireErrorMessage } = errors; @@ -89,6 +91,9 @@ class ContentfulModal extends React.Component { renderPlaceholder={LoadingIndicator} render={(data) => { const contentId = _.get(data, `entries.items.${id}.fields.content.sys.id`); + const hideDismissIcon = _.get(data, `entries.items.${id}.fields.hideDismissIcon`); + const hideCloseButton = _.get(data, `entries.items.${id}.fields.hideCloseButton`); + const extraStylesForContainer = _.get(data, `entries.items.${id}.fields.extraStylesForContainer`); if (!contentId) return null; return ( @@ -107,16 +112,20 @@ class ContentfulModal extends React.Component { onCancel={this.onCloseModal} theme={defaultModalTheme} > -
{ this.dismissButtonRef = node; }} - > - × -
+ { + !hideDismissIcon && ( +
{ this.dismissButtonRef = node; }} + > + × +
+ ) + } + { + !hideCloseButton && ( +
+ + CLOSE + +
+ ) + } )}
@@ -160,6 +185,7 @@ ContentfulModal.propTypes = { theme: PT.shape({ modalTrigger: PT.string, dismissButton: PT.any, + closeButton: PT.any, }), preview: PT.bool, spaceName: PT.string, diff --git a/src/shared/components/Contentful/Modal/modal-styles.scss b/src/shared/components/Contentful/Modal/modal-styles.scss new file mode 100644 index 0000000000..8e3bf60873 --- /dev/null +++ b/src/shared/components/Contentful/Modal/modal-styles.scss @@ -0,0 +1,188 @@ +@import "~styles/mixins"; + +$light-gray: #d4d4d4; + +.container { + @include roboto-regular; + + color: $tc-gray-50; + background: $tc-white; + border-radius: 10px; + top: 50%; + width: auto; + max-height: 90%; + overflow-y: auto; + + @media (max-width: 768px) { + width: 80%; + } + + h3 { + color: #1e94a3; + font-family: BarlowCondensed, sans-serif; + font-size: 34px; + font-weight: 500; + line-height: 38px; + text-align: center; + margin-bottom: 60px; + text-transform: uppercase; + + @media (max-width: 768px) { + font-size: 31px !important; + font-weight: 500 !important; + line-height: 33px !important; + margin-bottom: 30px; + } + } +} + +.overlay { + background-color: #2a2a2a; + opacity: 0.95; + border: none; + height: 100%; + left: 0; + outline: none; + position: fixed; + top: 0; + width: 100%; + z-index: 998; +} + +.podium-spot-wrapper { + text-align: center; + display: flex; + justify-content: center; + margin-bottom: 50px; + + > div > span { + height: 128px; + width: 128px; + + img, + svg { + border-radius: 50%; + border-style: solid; + border-width: 3px; + height: 100%; + width: 100%; + } + } +} + +.history-table { + width: 100%; + margin-bottom: 62px; + + thead { + th { + color: #2a2a2a; + font-family: Roboto, sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.5px; + line-height: 18px; + text-align: left; + text-transform: uppercase; + border-bottom: 1px solid #d4d4d4; + padding-bottom: 15px; + } + } + + .row { + border-bottom: 1px solid #d4d4d4; + + .name { + .link { + color: #0d61bf; + font-size: 14px; + font-weight: 400; + line-height: 51px; + text-decoration: underline; + + &:hover { + text-decoration: none; + } + } + } + + .placement { + color: #2a2a2a; + font-size: 14px; + font-weight: 500; + line-height: 51px; + } + + .points { + color: #2a2a2a; + font-size: 14px; + font-weight: 400; + line-height: 51px; + } + } +} + +.buttons { + margin-top: 10px; + display: flex; + flex-direction: row; + justify-content: center; + + .close-btn { + color: #fafafb; + font-family: Roboto, sans-serif; + font-size: 14px; + font-weight: 700; + letter-spacing: 0.8px; + line-height: 40px; + background-color: #137d60; + border-radius: 20px; + border: none; + text-transform: uppercase; + padding: 0 20px; + + &:hover { + background-color: #0ab88a !important; + box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2); + } + } +} + +.header-table-content { + display: flex; + align-items: center; +} + +.sort-container { + display: flex; + flex-direction: column; + margin-left: 5px; + padding: 0; + border: none; + outline: none; + background: transparent; +} + +.sort-container > div { + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; +} + +.sort-up { + border-bottom: 4px solid $light-gray; + margin-bottom: 2px; + + &.active { + border-bottom: 4px solid $tc-black; + } +} + +.sort-down { + border-top: 4px solid $light-gray; + + &.active { + border-top: 4px solid $tc-black; + } +} diff --git a/src/shared/components/Contentful/Modal/style.scss b/src/shared/components/Contentful/Modal/style.scss index c94207a613..4e5dea9b8b 100644 --- a/src/shared/components/Contentful/Modal/style.scss +++ b/src/shared/components/Contentful/Modal/style.scss @@ -25,3 +25,9 @@ opacity: 1; } } + +.closeButton { + display: flex; + justify-content: center; + margin: 40px 0 80px 0; +} diff --git a/src/shared/components/GUIKit/Assets/Images/checkmark-large.png b/src/shared/components/GUIKit/Assets/Images/checkmark-large.png new file mode 100644 index 0000000000..d3c90d8c6e Binary files /dev/null and b/src/shared/components/GUIKit/Assets/Images/checkmark-large.png differ diff --git a/src/shared/components/GUIKit/Assets/Images/checkmark-medium.png b/src/shared/components/GUIKit/Assets/Images/checkmark-medium.png new file mode 100644 index 0000000000..454fd20e7c Binary files /dev/null and b/src/shared/components/GUIKit/Assets/Images/checkmark-medium.png differ diff --git a/src/shared/components/GUIKit/Assets/Images/checkmark-small.png b/src/shared/components/GUIKit/Assets/Images/checkmark-small.png new file mode 100644 index 0000000000..fdcd176f25 Binary files /dev/null and b/src/shared/components/GUIKit/Assets/Images/checkmark-small.png differ diff --git a/src/shared/components/GUIKit/Assets/Images/icon-next.svg b/src/shared/components/GUIKit/Assets/Images/icon-next.svg index cb096e3a01..467efa406f 100644 --- a/src/shared/components/GUIKit/Assets/Images/icon-next.svg +++ b/src/shared/components/GUIKit/Assets/Images/icon-next.svg @@ -5,7 +5,7 @@ - + \ No newline at end of file diff --git a/src/shared/components/GUIKit/Assets/Images/icon-prev.svg b/src/shared/components/GUIKit/Assets/Images/icon-prev.svg index 6b3b226ad2..3e90c47088 100644 --- a/src/shared/components/GUIKit/Assets/Images/icon-prev.svg +++ b/src/shared/components/GUIKit/Assets/Images/icon-prev.svg @@ -7,7 +7,7 @@ - + \ No newline at end of file diff --git a/src/shared/components/GUIKit/Assets/Styles/Includes/_mixin.scss b/src/shared/components/GUIKit/Assets/Styles/Includes/_mixin.scss index d6d93533d2..5589e9dc31 100644 --- a/src/shared/components/GUIKit/Assets/Styles/Includes/_mixin.scss +++ b/src/shared/components/GUIKit/Assets/Styles/Includes/_mixin.scss @@ -3,6 +3,7 @@ $gui-kit-gray-30: #aaa; $gui-kit-gray-90: #2a2a2a; $gui-kit-level-2: #0ab88a; $gui-kit-level-5: #ef476f; +$gui-kit-active-label: #229174; @mixin textInputLabel { font-size: 12px; diff --git a/src/shared/components/GUIKit/Checkbox/index.jsx b/src/shared/components/GUIKit/Checkbox/index.jsx index be44e9fcde..d93062fb06 100644 --- a/src/shared/components/GUIKit/Checkbox/index.jsx +++ b/src/shared/components/GUIKit/Checkbox/index.jsx @@ -5,23 +5,26 @@ */ import React, { useRef, useState } from 'react'; import PT from 'prop-types'; -import IconCheckSolid from 'assets/images/check-mark.svg'; import _ from 'lodash'; import './style.scss'; - import { config } from 'topcoder-react-utils'; +import iconCheckL from '../Assets/Images/checkmark-large.png'; +import iconCheckM from '../Assets/Images/checkmark-medium.png'; +import iconCheckS from '../Assets/Images/checkmark-small.png'; + function Checkbox({ checked, onChange, size, + errorMsg, }) { const [checkedInternal, setCheckedInternal] = useState(checked); let sizeStyle = size === 'lg' ? 'lgSize' : null; - let checkmarkSize = size === 'lg' ? 16 : 0; + // eslint-disable-next-line no-nested-ternary + const imgSrc = size === 'xs' ? iconCheckS : (size === 'sm' ? iconCheckM : iconCheckL); if (!sizeStyle) { sizeStyle = size === 'xs' ? 'xsSize' : 'smSize'; - checkmarkSize = size === 'xs' ? 10 : 13; } const delayedOnChange = useRef( _.debounce((q, cb) => cb(q), config.GUIKIT.DEBOUNCE_ON_CHANGE_TIME), @@ -37,9 +40,10 @@ function Checkbox({ delayedOnChange(e.target.checked, onChange); }} /> -
- +
+ checkmark-icon
+ {errorMsg ? ({errorMsg}) : null} ); } @@ -48,12 +52,14 @@ Checkbox.defaultProps = { checked: false, onChange: () => {}, size: 'sm', + errorMsg: '', }; Checkbox.propTypes = { checked: PT.bool, onChange: PT.func, size: PT.oneOf(['xs', 'sm', 'lg']), + errorMsg: PT.string, }; export default Checkbox; diff --git a/src/shared/components/GUIKit/Checkbox/style.scss b/src/shared/components/GUIKit/Checkbox/style.scss index 7d6801f888..28da3d2e24 100644 --- a/src/shared/components/GUIKit/Checkbox/style.scss +++ b/src/shared/components/GUIKit/Checkbox/style.scss @@ -8,20 +8,16 @@ background-color: $tc-white; border: 1px solid $gui-kit-gray-30; + &.haveError { + border: 2px solid #ef476f; + } + /* Create the checkmark/indicator (hidden when not checked) */ .after { position: absolute; display: none; left: 50%; top: 50%; - -webkit-filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)); - filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.35)); - - :global { - path { - fill: $tc-white; - } - } } } @@ -46,8 +42,8 @@ border-radius: 4px; .after { - margin-left: -8px; - margin-top: -9px; + margin-left: -9px; + margin-top: -7px; } } } @@ -63,8 +59,8 @@ border-radius: 3px; .after { - margin-left: -6px; - margin-top: -7px; + margin-left: -7px; + margin-top: -6px; } } } @@ -80,8 +76,8 @@ border-radius: 2px; .after { - margin-left: -5px; - margin-top: -5px; + margin-left: -6px; + margin-top: -4px; } } } @@ -107,3 +103,14 @@ } } } + +.errorMessage { + display: block; + + @include errorMessage; + + position: absolute; + white-space: nowrap; + margin: 35px 0 0 0; + color: #ef476f; +} diff --git a/src/shared/components/GUIKit/Datepicker/index.jsx b/src/shared/components/GUIKit/Datepicker/index.jsx index 53bc0887ae..5cf376d7ef 100644 --- a/src/shared/components/GUIKit/Datepicker/index.jsx +++ b/src/shared/components/GUIKit/Datepicker/index.jsx @@ -11,9 +11,13 @@ import 'react-dates/initialize'; import { SingleDatePicker } from 'react-dates'; import IconCalendar from 'assets/images/tc-edu/icon-calendar.svg'; import useWindowSize from 'utils/useWindowSize'; +import CalendarWeek from 'react-dates/lib/components/CalendarWeek'; import IconNext from '../Assets/Images/icon-next.svg'; import IconPrev from '../Assets/Images/icon-prev.svg'; +// eslint-disable-next-line no-unused-expressions, react/forbid-foreign-prop-types +CalendarWeek && CalendarWeek.propTypes && delete CalendarWeek.propTypes.children; // fixing the bug in react-dates, more detail in here https://github.com/airbnb/react-dates/issues/1121 + function Datepicker({ value, placeholder, @@ -27,7 +31,6 @@ function Datepicker({ const { width } = useWindowSize(); return (
} navNext={} displayFormat="MMM DD, YYYY" - daySize={width > 600 ? 53 : 35} + daySize={width > 600 ? 47 : 35} renderDayContents={d => (
{d.date ? d.date() : ''}
)} enableOutsideDays firstDayOfWeek={1} diff --git a/src/shared/components/GUIKit/Datepicker/style.scss b/src/shared/components/GUIKit/Datepicker/style.scss index 25a0f7a9e5..320798b212 100644 --- a/src/shared/components/GUIKit/Datepicker/style.scss +++ b/src/shared/components/GUIKit/Datepicker/style.scss @@ -4,6 +4,7 @@ position: relative; display: flex; flex-direction: column; + flex-grow: 1; .label { @include textInputLabel; @@ -26,6 +27,12 @@ :global { @import '~react-dates/lib/css/_datepicker.css'; + .SingleDatePicker { + width: 100%; + position: relative; + display: inline-block; + } + button { &:focus { outline: none; @@ -34,6 +41,8 @@ .SingleDatePickerInput { position: relative; + border: none; + width: 100%; .SingleDatePickerInput_calendarIcon { position: absolute; @@ -44,9 +53,29 @@ width: 48px; bottom: 0; z-index: 1; + background: 0 0; + border: 0; + color: inherit; + font: inherit; + line-height: normal; + overflow: visible; + cursor: pointer; + display: inline-block; + vertical-align: middle; } .DateInput { + padding: 0; + width: 100%; + font-family: "Roboto", Helvetica, Arial, sans-serif; + font-weight: 400; + font-size: 15px; + margin: 0; + background: #fff; + position: relative; + display: inline-block; + vertical-align: middle; + input { @include textInput; @@ -57,32 +86,103 @@ .DateInput_fang { display: none; } + + .DateInput_screenReaderMessage { + border: 0; + clip: rect(0, 0, 0, 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; + } } .SingleDatePicker_picker { z-index: 7; top: 64px !important; + background-color: #fff; + position: absolute; + + .DayPicker { + background: #fff; + position: relative; + text-align: left; + + .DayPicker_weekHeaders__horizontal { + margin-left: 9px; + + .DayPicker_weekHeader { + color: #757575; + position: absolute; + top: 62px; + z-index: 2; + text-align: left; + + .DayPicker_weekHeader_ul { + list-style: none; + margin: 1px 0; + padding-left: 0; + padding-right: 0; + font-size: 14px; + + .DayPicker_weekHeader_ul { + list-style: none; + margin: 1px 0; + padding-left: 0; + padding-right: 0; + font-size: 14px; + display: inline-block; + text-align: center; + } + } + } + } + + .DayPicker_weekHeaders { + position: relative; + + .DayPicker_weekHeader_li { + font-size: 15px; + font-family: "Roboto", Helvetica, Arial, sans-serif; + font-weight: 400; + line-height: 20px; + display: inline-block; + text-align: center; + } + } + + .DayPicker_focusRegion { + outline: 0; + } + } .DayPicker__withBorder { border: 1px solid $gui-kit-gray-30; box-shadow: 2px 2px 3px 0 $tc-gray-neutral-light; overflow: hidden; + border-radius: 3px; + } + + .DayPickerNavigation__horizontal { + height: 0; + } + + .DayPickerNavigation { + position: relative; + z-index: 2; } .DayPickerNavigation_button { position: absolute; top: 18px; + cursor: pointer; &:focus { outline: none; } - svg { - path { - fill: $gui-kit-level-2; - } - } - &:first-child { left: 18px; } @@ -99,6 +199,9 @@ padding-bottom: 10px; margin-bottom: 45px; position: relative; + color: $gui-kit-gray-90; + text-align: center; + caption-side: initial; &::after { content: ''; @@ -107,16 +210,17 @@ left: -5px; width: calc(100% + 10px); height: 1px; - background-color: $tc-gray-20; + background-color: #d4d4d4; } } - + /* stylelint-disable */ .DayPicker_weekHeader { .DayPicker_weekHeader_ul { .DayPicker_weekHeader_li { small { color: $gui-kit-gray-90; font-weight: 500 !important; + font-size: 16px; } } } @@ -124,14 +228,46 @@ .DayPicker_focusRegion { .DayPicker_transitionContainer { + position: relative; + overflow: hidden; + border-radius: 3px; + &.DayPicker_transitionContainer__horizontal { transition: none !important; } .CalendarMonthGrid { + background: #fff; + text-align: left; + z-index: 0; + position: absolute; + left: 9px; + .CalendarMonthGrid_month__horizontal { + display: inline-block; + vertical-align: top; + min-height: 100%; + + &.CalendarMonthGrid_month__hidden { + visibility: hidden; + } + + &.CalendarMonthGrid_month__hideForAnimation { + position: absolute; + z-index: -1; + opacity: 0; + pointer-events: none; + } + .CalendarMonth { + background: #fff; + text-align: center; + vertical-align: top; + user-select: none; + table.CalendarMonth_table { + margin-bottom: 0; + tbody { tr { td { @@ -155,6 +291,7 @@ &.CalendarDay__default { color: #2a2a2a; + padding: 0; } &.CalendarDay__blocked_out_of_range { @@ -171,11 +308,12 @@ } &.CalendarDay__outside { - color: $tc-gray-20; + color: $gui-kit-gray-30; } &:not(.CalendarDay__outside):not(.CalendarDay__blocked_out_of_range):not(.CalendarDay__selected):hover { background: transparent !important; + cursor: pointer; div { background: $tc-gray-05; @@ -190,6 +328,7 @@ } } } + /* stylelint-enable */ } } } diff --git a/src/shared/components/GUIKit/Dropdown/style.scss b/src/shared/components/GUIKit/Dropdown/style.scss index 7ca3bf762b..79a2fc4451 100644 --- a/src/shared/components/GUIKit/Dropdown/style.scss +++ b/src/shared/components/GUIKit/Dropdown/style.scss @@ -27,6 +27,7 @@ padding-top: 12px; &.haveValue .label, + &.haveError .label, &.isFocused .label { display: flex; } @@ -147,6 +148,7 @@ line-height: 30px !important; color: $gui-kit-gray-90 !important; background-color: transparent !important; + text-decoration: none !important; &.is-selected { font-weight: bold !important; diff --git a/src/shared/components/GUIKit/DropdownTerms/style.scss b/src/shared/components/GUIKit/DropdownTerms/style.scss index 121aa54da7..d06e106b10 100644 --- a/src/shared/components/GUIKit/DropdownTerms/style.scss +++ b/src/shared/components/GUIKit/DropdownTerms/style.scss @@ -149,6 +149,7 @@ :global { .Select-menu-outer { border: none !important; + padding-bottom: 10px !important; } } } diff --git a/src/shared/components/GUIKit/FilePicker/index.jsx b/src/shared/components/GUIKit/FilePicker/index.jsx new file mode 100644 index 0000000000..19d9fa1050 --- /dev/null +++ b/src/shared/components/GUIKit/FilePicker/index.jsx @@ -0,0 +1,77 @@ +/** + * GUIKit FilePicker based on filestack-react + */ + +import React from 'react'; +import PT from 'prop-types'; +import Dropzone from 'react-dropzone'; + +import './styles.scss'; + +/** + * FilestackFilePicker component + */ +function FilestackFilePicker({ + onFilePick, + btnText, + infoText, + options, + errorMsg, + inputOptions, + file, +}) { + let fileName = file ? file.name : null; + return ( + + { + fileName = acceptedFiles[0].name; + onFilePick(acceptedFiles); + }} + {...options} + > + {({ getRootProps, getInputProps }) => ( +
+ + { + fileName ? ( +

{fileName}

+ ) : ( +

+ {infoText} + OR +

+ ) + } + +
+ )} +
+ {errorMsg ? ({errorMsg}) : null} +
+ ); +} + +FilestackFilePicker.defaultProps = { + infoText: '', + btnText: 'SELECT A FILE', + options: {}, + errorMsg: '', + inputOptions: {}, + file: null, +}; + +/** + * Prop Validation + */ +FilestackFilePicker.propTypes = { + infoText: PT.string, + btnText: PT.string, + onFilePick: PT.func.isRequired, + options: PT.shape(), + errorMsg: PT.string, + inputOptions: PT.shape(), + file: PT.shape(), +}; + +export default FilestackFilePicker; diff --git a/src/shared/components/GUIKit/FilePicker/styles.scss b/src/shared/components/GUIKit/FilePicker/styles.scss new file mode 100644 index 0000000000..9906a1e66e --- /dev/null +++ b/src/shared/components/GUIKit/FilePicker/styles.scss @@ -0,0 +1,57 @@ +@import '~components/buttons/themed/tc.scss'; +@import '~components/GUIKit/Assets/Styles/default'; + +.container { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + background-color: #fff; + border-radius: 6px; + min-height: 164px; + background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='6' ry='6' stroke='gray' stroke-width='1' stroke-dasharray='5%2c5' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e"); + outline: none !important; + padding: 25px 15px 40px 15px; + + &.hasError { + background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='6' ry='6' stroke='red' stroke-width='1' stroke-dasharray='5%2c5' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e"); + } + + .btn { + outline: none !important; + + @include primary-white; + @include sm; + + &:hover { + @include primary-white; + } + } + + .infoText { + color: #aaa; + font-family: Roboto, sans-serif; + font-size: 16px !important; + line-height: 22px !important; + margin: 0 !important; + display: flex; + flex-direction: column; + align-items: center; + + > span { + display: flex; + margin: 19px 0; + } + + &.withFile { + color: #2a2a2a; + margin: 40px 0 !important; + } + } +} + +.errorMessage { + display: block; + + @include errorMessage; +} diff --git a/src/shared/components/GUIKit/RadioButton/index.jsx b/src/shared/components/GUIKit/RadioButton/index.jsx index fb2df581fb..cf0744294b 100644 --- a/src/shared/components/GUIKit/RadioButton/index.jsx +++ b/src/shared/components/GUIKit/RadioButton/index.jsx @@ -10,7 +10,9 @@ import './style.scss'; import { config } from 'topcoder-react-utils'; -function RadioButton({ options, onChange, size }) { +function RadioButton({ + options, onChange, size, errorMsg, +}) { const [internalOptions, setInternalOptions] = useState(options); const optionsWithKey = internalOptions.map((o, oIndex) => ({ ...o, key: oIndex })); let sizeStyle = size === 'lg' ? 'lgSize' : null; @@ -22,34 +24,38 @@ function RadioButton({ options, onChange, size }) { ).current; return ( -
- {optionsWithKey.map(o => ( -
- - {o.label ? ({o.label}) : null} -
- ))} -
+ +
+ {optionsWithKey.map(o => ( +
+ + {o.label ? ({o.label}) : null} +
+ ))} +
+ {errorMsg ? ({errorMsg}) : null} +
); } RadioButton.defaultProps = { onChange: () => {}, size: 'sm', + errorMsg: '', }; RadioButton.propTypes = { @@ -61,6 +67,7 @@ RadioButton.propTypes = { ).isRequired, onChange: PT.func, size: PT.oneOf(['xs', 'sm', 'lg']), + errorMsg: PT.string, }; export default RadioButton; diff --git a/src/shared/components/GUIKit/RadioButton/style.scss b/src/shared/components/GUIKit/RadioButton/style.scss index 63705c163d..cbe0ebdaa9 100644 --- a/src/shared/components/GUIKit/RadioButton/style.scss +++ b/src/shared/components/GUIKit/RadioButton/style.scss @@ -24,6 +24,10 @@ background-color: $tc-white; box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.35); } + + &.hasError { + border: 2px solid $gui-kit-level-5; + } } .radioButton { @@ -33,6 +37,7 @@ .label { font-size: 14px; + cursor: pointer; } /* The container */ @@ -70,6 +75,10 @@ display: flex; flex-direction: column; + .label { + color: $gui-kit-gray-90; + } + // lg size &.lgSize { .container { @@ -91,7 +100,7 @@ } .label { - margin-left: 15px; + margin-left: 8px; } } @@ -116,7 +125,7 @@ } .label { - margin-left: 10px; + margin-left: 8px; } } @@ -141,7 +150,16 @@ } .label { - margin-left: 10px; + margin-left: 8px; } } } + +.errorMessage { + display: block; + + @include errorMessage; + + color: #ef476f; + margin-left: 0; +} diff --git a/src/shared/components/GUIKit/TextInput/index.jsx b/src/shared/components/GUIKit/TextInput/index.jsx index 95a86dd516..6194c0d27e 100644 --- a/src/shared/components/GUIKit/TextInput/index.jsx +++ b/src/shared/components/GUIKit/TextInput/index.jsx @@ -30,7 +30,7 @@ function TextInput({ defaultValue={value} type="text" placeholder={`${placeholder}${placeholder && required ? ' *' : ''}`} - styleName={`${val ? 'haveValue' : ''} ${errorMsg ? 'haveError' : ''}`} + styleName={`${value || val ? 'haveValue' : ''} ${errorMsg ? 'haveError' : ''}`} onChange={(e) => { delayedOnChange(e.target.value, onChange); setVal(e.target.value); diff --git a/src/shared/components/GUIKit/TextInput/style.scss b/src/shared/components/GUIKit/TextInput/style.scss index 453370fbb8..7089935faf 100644 --- a/src/shared/components/GUIKit/TextInput/style.scss +++ b/src/shared/components/GUIKit/TextInput/style.scss @@ -41,12 +41,13 @@ } input:not([type='checkbox']).haveValue + label, + input:not([type='checkbox']).haveError + label, input:not([type='checkbox']):focus + label { display: flex; } input:not([type='checkbox']):focus + label { - color: $gui-kit-level-2; + color: $gui-kit-active-label; } input:not([type='checkbox']).haveError + label, diff --git a/src/shared/components/Gigs/GigApply/index.jsx b/src/shared/components/Gigs/GigApply/index.jsx new file mode 100644 index 0000000000..29adef4521 --- /dev/null +++ b/src/shared/components/Gigs/GigApply/index.jsx @@ -0,0 +1,310 @@ +/** + * The Gig apply page. + */ + +import _ from 'lodash'; +import React from 'react'; +import PT from 'prop-types'; +import { Link, config } from 'topcoder-react-utils'; +import TextInput from 'components/GUIKit/TextInput'; +import Datepicker from 'components/GUIKit/Datepicker'; +import DropdownTerms from 'components/GUIKit/DropdownTerms'; +import RadioButton from 'components/GUIKit/RadioButton'; +import Checkbox from 'components/GUIKit/Checkbox'; +import { getCustomField } from 'utils/gigs'; +import Modal from 'components/Contentful/Modal'; +import FilestackFilePicker from 'components/GUIKit/FilePicker'; +import Dropdown from 'components/GUIKit/Dropdown'; +import LoadingIndicator from 'components/LoadingIndicator'; +import './style.scss'; +import bigCheckmark from 'assets/images/big-checkmark.png'; +import SadFace from 'assets/images/sad-face-icon.svg'; +import BackArrowGig from 'assets/images/back-arrow-gig-apply.svg'; + +export default function GigApply(props) { + const { + job, onFormInputChange, formData, formErrors, onApplyClick, applying, application, + } = props; + + return ( +
+ { + job.error || job.enable_job_application_form !== 1 ? ( +
+

Gig does not exist.

+
+ VIEW OTHER GIGS +
+
+ ) : ( +
+

{job.name}

+ GIG DETAILS +
+ { + application ? ( +
+ { application.error ? : bigCheckmark OK } +

{application.error ? 'OOPS!' : 'APPLICATION SUBMITTED'}

+ { + application.error ? ( +

Looks like there is a problem on our end. Please try again.
If this persists please contact support@topcoder.com.

+ ) : ( +

We will contact you via email if it seems like a fit!

+ ) + } +
+ { + application.error ? ( + { + e.preventDefault(); + window.location.reload(); + }} + >APPLY AGAIN + + ) : ( + GO TO GIG LIST + ) + } +
+
+ ) : null + } + { + applying ? ( +
+ +

Processing your application…

+
+ ) : null + } + { + !application && !applying ? ( +
+

PERSONAL INFORMATION

+

Welcome to Topcoder Gigs! We’d like to get to know you.

+
+
+ onFormInputChange('fname', val)} + errorMsg={formErrors.fname} + value={formData.fname} + required + /> + onFormInputChange('lname', val)} + errorMsg={formErrors.lname} + value={formData.lname} + required + /> +
+
+ onFormInputChange('email', val)} + errorMsg={formErrors.email} + value={formData.email} + required + /> + onFormInputChange('phone', val)} + errorMsg={formErrors.phone} + value={formData.phone} + required + /> +
+
+ onFormInputChange('city', val)} + errorMsg={formErrors.city} + value={formData.city} + required + /> + onFormInputChange('country', val)} + errorMsg={formErrors.country} + options={formData.country} + required + /> +
+
+

TOPCODER INFORMATION

+

If you have a Topcoder profile, please share. Not a Member?

+
+
+ onFormInputChange('handle', val)} + errorMsg={formErrors.handle} + value={formData.handle} + /> + onFormInputChange('tcProfileLink', val)} + errorMsg={formErrors.tcProfileLink} + value={formData.handle ? `topcoder.com/members/${formData.handle}` : null} + /> +
+
+

SHARE YOUR EXPECTATIONS

+

Your Professional Work History

+
+
+ onFormInputChange('payExpectation', val)} + errorMsg={formErrors.payExpectation} + value={formData.payExpectation} + /> + onFormInputChange('availFrom', val ? val.toISOString() : null)} + errorMsg={formErrors.availFrom} + value={formData.availFrom} + /> +
+
+

RESUME & SKILLS

+

Upload Your Resume or CV

+
+ onFormInputChange('fileCV', files[0])} + inputOptions={{ + accept: '.pdf,.docx', + }} + infoText="Drag & drop your resume or CV here - please omit contact information *" + errorMsg={formErrors.fileCV} + /> +
+ onFormInputChange('skills', val)} + errorMsg={formErrors.skills} + addNewOptionPlaceholder="Type to add another skill..." + required + /> +
+

FINAL QUESTIONS

+

Please Complete the Following Questions

+
+ onFormInputChange('reffereal', val)} + errorMsg={formErrors.reffereal} + value={formData.reffereal} + required + /> +
+ onFormInputChange('whyFit', val)} + errorMsg={formErrors.whyFit} + value={formData.whyFit} + /> +

What is your availability per week?

+
+ { + _.map(formData.timeAvailability, (cbox, indx) => ( +
+ { + formData.timeAvailability[indx].checked = val; + onFormInputChange('timeAvailability', formData.timeAvailability); + }} + checked={formData.timeAvailability[indx].checked} + size="lg" + /> + {cbox.label} +
+ )) + } +
+

Are you able to work during the specified timezone? ({`${getCustomField(job.custom_fields, 'Timezone')}`})

+ onFormInputChange('timezoneConfirm', val)} + errorMsg={formErrors.timezoneConfirm} + options={formData.timezoneConfirm} + size="lg" + /> +

Are you ok to work with the duration of the gig? ({`${getCustomField(job.custom_fields, 'Duration')}`})

+ onFormInputChange('durationConfirm', val)} + errorMsg={formErrors.durationConfirm} + options={formData.durationConfirm} + size="lg" + /> +
+ onFormInputChange('notes', val)} + errorMsg={formErrors.notes} + /> +
+
+
+
+
+
+ onFormInputChange('agreedTerms', val)} + checked={formData.agreedTerms} + errorMsg={formErrors.agreedTerms} + size="lg" + /> + I agree to Candidate Terms * +
+
+ View Our Equal Employment Opportunity Policy +
+ +
+ ) : null + } +
+ ) + } +
+ ); +} + +GigApply.defaultProps = { + formErrors: {}, + applying: false, + application: null, +}; + +GigApply.propTypes = { + job: PT.shape().isRequired, + formErrors: PT.shape(), + formData: PT.shape().isRequired, + onFormInputChange: PT.func.isRequired, + onApplyClick: PT.func.isRequired, + applying: PT.bool, + application: PT.shape(), +}; diff --git a/src/shared/components/Gigs/GigApply/style.scss b/src/shared/components/Gigs/GigApply/style.scss new file mode 100644 index 0000000000..c5b059206c --- /dev/null +++ b/src/shared/components/Gigs/GigApply/style.scss @@ -0,0 +1,277 @@ +@import '~styles/mixins'; +@import "~components/Contentful/default"; + +.loading-wrap { + margin-top: 35px; + + .loading-text { + font-family: Roboto, sans-serif; + font-size: 24px; + line-height: 26px; + color: #2a2a2a; + text-align: center; + margin-top: 26px; + } +} + +.container { + max-width: $screen-lg; + min-height: 80vh; + margin: auto; + color: #2a2a2a; + + @include gui-kit-headers; + @include gui-kit-content; + @include roboto-regular; + + @include xs-to-md { + padding: 0 15px; + } + + .error { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 27px; + height: 80vh; + } + + .checkboxes-row { + display: flex; + + .checkbox { + display: flex; + align-items: center; + margin-right: 28px; + + &:last-child { + margin-right: 0; + } + + .label { + font-size: 14px; + margin-left: 8px; + } + } + } + + .wrap { + h2 { + color: #26b3c5; + text-align: center; + margin-top: 47px; + margin-bottom: 31px; + + @include xs-to-md { + text-align: left; + } + } + + .back-link { + color: #229174; + font-weight: bold; + font-size: 14px; + letter-spacing: 0.8px; + text-decoration: none; + line-height: 40px; + margin-bottom: 12px; + display: flex; + align-items: center; + + svg { + width: 8px; + margin-right: 6px; + } + } + + .separator { + border-bottom: 1px solid #e9e9e9; + } + + .apply-state { + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: 162px; + margin-top: 50px; + + h2 { + color: #2a2a2a; + margin-top: 31px; + margin-bottom: 20px; + } + + p { + font-size: 24px; + line-height: 36px; + text-align: center; + + a { + font-size: 24px; + line-height: 36px; + } + } + + .cta-buttons { + margin-top: 30px; + } + } + + .form-wrap { + max-width: 880px; + margin: auto; + + h4 { + margin-top: 35px; + margin-bottom: 5px; + } + + p { + font-size: 14px; + line-height: 22px; + + a { + font-size: 14px; + } + } + + .form-section { + margin: 13px 0 50px; + + .form-row { + display: grid; + gap: 20px; + grid-template-columns: 1fr 1fr; + margin-bottom: 8px; + + @include xs-to-md { + display: flex; + flex-direction: column; + } + } + + p { + margin-top: 30px; + margin-bottom: 6px; + line-height: 30px; + font-size: 16px; + } + + :global { + .radioButtonContainer { + display: flex; + flex-direction: row; + + .radioButton { + margin-right: 28px; + + &:last-child { + margin-right: 0; + } + } + } + } + + .last-input { + margin-top: 34px; + margin-bottom: 81px; + } + + .input-bot-margin { + margin-bottom: 10px; + } + } + } + + .bottom-section { + display: flex; + justify-content: space-between; + font-size: 14px; + margin-top: 23px; + margin-bottom: 80px; + + @include xs-to-md { + flex-direction: column; + + .checkboxes-row, + .checkbox { + margin-bottom: 20px; + } + } + } + + .primaryBtn { + background-color: #137d60; + border-radius: 20px; + color: #fff; + font-size: 14px; + font-weight: bolder; + text-decoration: none; + text-transform: uppercase; + line-height: 40px; + padding: 0 20px; + border: none; + outline: none; + margin: 0 auto 150px auto !important; + display: flex; + + &:hover { + box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2); + background-color: #0ab88a; + } + + @include xs-to-sm { + margin-bottom: 20px; + } + + &:disabled { + background-color: #e9e9e9 !important; + border: none !important; + text-decoration: none !important; + color: #fafafb !important; + box-shadow: none !important; + } + } + + .moldal-link { + text-decoration: underline; + color: #0d61bf; + + &:hover { + text-decoration: none; + } + } + } +/* stylelint-disable */ + .cta-buttons { + display: flex; + justify-content: center; + margin-top: 47px; + + @include xs-to-sm { + flex-direction: column; + } + + a { + background-color: #fff; + border: 1px solid #137d60; + border-radius: 20px; + color: #229174; + font-size: 14px; + font-weight: bolder; + text-decoration: none; + text-transform: uppercase; + line-height: 40px; + padding: 0 20px; + + &:hover { + box-shadow: 0 1px 5px 0 rgba(0, 0, 0, 0.2); + } + + @include xs-to-sm { + text-align: center; + } + } + } + /* stylelint-enable */ +} diff --git a/src/shared/components/Gigs/GigDetails.jsx b/src/shared/components/Gigs/GigDetails/index.jsx similarity index 92% rename from src/shared/components/Gigs/GigDetails.jsx rename to src/shared/components/Gigs/GigDetails/index.jsx index df0b06f343..2471687f42 100644 --- a/src/shared/components/Gigs/GigDetails.jsx +++ b/src/shared/components/Gigs/GigDetails/index.jsx @@ -22,6 +22,7 @@ import iconSkills from 'assets/images/icon-skills-blue.png'; import iconLabel1 from 'assets/images/l1.png'; import iconLabel2 from 'assets/images/l2.png'; import iconLabel3 from 'assets/images/l3.png'; +import SadFace from 'assets/images/sad-face-icon.svg'; // Cleanup HTML from style tags // so it won't affect other parts of the UI @@ -35,7 +36,7 @@ const ReactHtmlParserOptions = { }; export default function GigDetails(props) { - const { job } = props; + const { job, application } = props; let shareUrl; if (isomorphy.isClientSide()) { shareUrl = encodeURIComponent(window.location.href); @@ -48,7 +49,8 @@ export default function GigDetails(props) { { job.error || job.enable_job_application_form !== 1 ? (
-

Gig does not exist.

+ { job.error ? : null } +

{ job.error ? 'Gig does not exist' : 'This Gig has been Fulfilled'}

VIEW OTHER GIGS
@@ -110,7 +112,11 @@ export default function GigDetails(props) {
- APPLY TO THIS JOB + { + !application || !application.success ? ( + APPLY TO THIS JOB + ) : null + } VIEW OTHER JOBS
@@ -159,6 +165,11 @@ export default function GigDetails(props) { ); } +GigDetails.defaultProps = { + application: null, +}; + GigDetails.propTypes = { job: PT.shape().isRequired, + application: PT.shape(), }; diff --git a/src/shared/components/Gigs/style.scss b/src/shared/components/Gigs/GigDetails/style.scss similarity index 100% rename from src/shared/components/Gigs/style.scss rename to src/shared/components/Gigs/GigDetails/style.scss diff --git a/src/shared/containers/Gigs/RecruitCRMJobApply.jsx b/src/shared/containers/Gigs/RecruitCRMJobApply.jsx new file mode 100644 index 0000000000..67a67e792d --- /dev/null +++ b/src/shared/containers/Gigs/RecruitCRMJobApply.jsx @@ -0,0 +1,222 @@ +/** + * Apply for a job page + */ + +import _ from 'lodash'; +import actions from 'actions/recruitCRM'; +import GigApply from 'components/Gigs/GigApply'; +import PT from 'prop-types'; +import React from 'react'; +import { connect } from 'react-redux'; +import { isValidEmail } from 'utils/tc'; +import techSkills from './techSkills'; + +const countries = require('i18n-iso-countries'); +countries.registerLocale(require('i18n-iso-countries/langs/en.json')); + +class RecruitCRMJobApplyContainer extends React.Component { + constructor(props) { + super(props); + // initial state + this.state = { + formErrors: {}, + formData: { + availFrom: new Date().toISOString(), + skills: _.map(techSkills, label => ({ label, selected: false })), + durationConfirm: [{ label: 'Yes', value: false }, { label: 'No', value: false }], + timezoneConfirm: [{ label: 'Yes', value: false }, { label: 'No', value: false }], + timeAvailability: [ + { label: '10 hours', checked: false }, { label: '20 hours', checked: false }, { label: '30 hours', checked: false }, { label: '40 hours', checked: false }, + ], + agreedTerms: false, + country: _.map(countries.getNames('en'), val => ({ label: val, selected: false })), + // eslint-disable-next-line react/destructuring-assignment + }, + }; + + // binds + this.onFormInputChange = this.onFormInputChange.bind(this); + this.onApplyClick = this.onApplyClick.bind(this); + this.validateForm = this.validateForm.bind(this); + } + + componentDidMount() { + const { formData } = this.state; + const { user } = this.props; + this.setState({ + formData: _.merge(formData, user), + }); + } + + onFormInputChange(key, value) { + // update the state + this.setState(state => ({ + ...state, + formData: { + ...state.formData, + [key]: value, + }, + })); + this.validateForm(key); + } + + onApplyClick() { + const { applyForJob, job } = this.props; + const { formData } = this.state; + this.validateForm(); + this.setState((state) => { + if (_.isEmpty(state.formErrors)) { + applyForJob(job, formData); + } + }); + } + + validateForm(prop) { + this.setState((state) => { + const { formData, formErrors } = state; + // Form validation happens here + const requiredTextFields = [ + 'fname', 'lname', 'city', 'reffereal', 'phone', 'email', + ]; + // check required text fields for value + // check min/max lengths + _.each(requiredTextFields, (key) => { + // validate only modified prop if set + // and do not touch the others + if (prop && prop !== key) return; + if (!formData[key] || !_.trim(formData[key])) formErrors[key] = 'Required field'; + else if (formData[key] && _.trim(formData[key]).length < 2) formErrors[key] = 'Must be at least 2 characters'; + else if (formData[key] && _.trim(formData[key]).length > 2) { + switch (key) { + case 'reffereal': + if (_.trim(formData[key]).length > 2000) formErrors[key] = 'Must be max 2000 characters'; + else delete formErrors[key]; + break; + case 'city': + case 'phone': + if (_.trim(formData[key]).length > 50) formErrors[key] = 'Must be max 50 characters'; + else delete formErrors[key]; + break; + default: + if (_.trim(formData[key]).length > 40) formErrors[key] = 'Must be max 40 characters'; + else delete formErrors[key]; + break; + } + } else delete formErrors[key]; + }); + // check for selected country + if (!prop || prop === 'country') { + if (!_.find(formData.country, { selected: true })) formErrors.country = 'Please, select your country'; + else delete formErrors.country; + } + // check payExpectation to be a number + if (!prop || prop === 'payExpectation') { + if (formData.payExpectation && _.trim(formData.payExpectation)) { + if (!_.isInteger(_.toNumber(formData.payExpectation))) formErrors.payExpectation = 'Must be integer value in $'; + else delete formErrors.payExpectation; + } else delete formErrors.payExpectation; + } + // check for valid email + if (!prop || prop === 'email') { + if (formData.email && _.trim(formData.email)) { + if (!(isValidEmail(formData.email))) formErrors.email = 'Invalid email'; + else delete formErrors.email; + } + } + // require atleast 1 skill + if (!prop || prop === 'skills') { + if (!_.find(formData.skills, { selected: true })) formErrors.skills = 'Please, add technical skills'; + else delete formErrors.skills; + } + // have accepted terms + if (!prop || prop === 'agreedTerms') { + if (!formData.agreedTerms) formErrors.agreedTerms = 'Please, accept our terms'; + else delete formErrors.agreedTerms; + } + // has CV file ready for upload + if (!prop || prop === 'fileCV') { + if (!formData.fileCV) formErrors.fileCV = 'Please, pick your CV file for uploading'; + else { + const sizeInMB = (formData.fileCV.size / (1024 * 1024)).toFixed(2); + if (sizeInMB > 8) { + formErrors.fileCV = 'Max file size is limited to 8 MB'; + delete formData.fileCV; + } else if (_.endsWith(formData.fileCV.name, '.pdf') || _.endsWith(formData.fileCV.name, '.docx')) { + delete formErrors.fileCV; + } else { + formErrors.fileCV = 'Only .pdf and .docx files are allowed'; + } + } + } + // updated state + return { + ...state, + formErrors, + }; + }); + } + + render() { + const { formErrors, formData } = this.state; + return ( + + ); + } +} + +RecruitCRMJobApplyContainer.defaultProps = { + user: null, + applying: false, + application: null, +}; + +RecruitCRMJobApplyContainer.propTypes = { + job: PT.shape().isRequired, + user: PT.shape(), + applyForJob: PT.func.isRequired, + applying: PT.bool, + application: PT.shape(), +}; + +function mapStateToProps(state, ownProps) { + const { profile } = state.auth; + const { job } = ownProps; + let userData = null; + if (profile && profile.email) { + userData = { + fname: profile.firstName, + lname: profile.lastName, + email: profile.email, + city: profile.addresses && profile.addresses[0] ? profile.addresses[0].city : '', + handle: profile.handle, + }; + } + return { + user: userData, + applying: state.recruitCRM && state.recruitCRM[job.slug] + ? state.recruitCRM[job.slug].applying : false, + application: state.recruitCRM && state.recruitCRM[job.slug] + ? state.recruitCRM[job.slug].application : null, + }; +} + +function mapDispatchToActions(dispatch) { + const a = actions.recruit; + return { + applyForJob: (job, payload) => { + dispatch(a.applyForJobInit(job, payload)); + dispatch(a.applyForJobDone(job, payload)); + }, + }; +} + +export default connect( + mapStateToProps, + mapDispatchToActions, +)(RecruitCRMJobApplyContainer); diff --git a/src/shared/containers/Gigs/RecruitCRMJobDetails.jsx b/src/shared/containers/Gigs/RecruitCRMJobDetails.jsx index b69aae2efe..7be3ddf9f6 100644 --- a/src/shared/containers/Gigs/RecruitCRMJobDetails.jsx +++ b/src/shared/containers/Gigs/RecruitCRMJobDetails.jsx @@ -3,39 +3,48 @@ * driven by recruitCRM */ +import _ from 'lodash'; import actions from 'actions/recruitCRM'; import LoadingIndicator from 'components/LoadingIndicator'; import GigDetails from 'components/Gigs/GigDetails'; import PT from 'prop-types'; import React from 'react'; import { connect } from 'react-redux'; +import RecruitCRMJobApply from './RecruitCRMJobApply'; class RecruitCRMJobDetailsContainer extends React.Component { componentDidMount() { const { getJob, id, + job, } = this.props; - getJob(id); + if (_.isEmpty(job)) { + getJob(id); + } } render() { const { loading, job, + isApply, + application, } = this.props; if (loading) { return ; } - return ; + return isApply + ? : ; } } RecruitCRMJobDetailsContainer.defaultProps = { job: {}, + application: null, }; RecruitCRMJobDetailsContainer.propTypes = { @@ -43,6 +52,8 @@ RecruitCRMJobDetailsContainer.propTypes = { loading: PT.bool.isRequired, job: PT.shape(), id: PT.string.isRequired, + isApply: PT.bool.isRequired, + application: PT.shape(), }; function mapStateToProps(state, ownProps) { @@ -50,6 +61,7 @@ function mapStateToProps(state, ownProps) { return { job: data ? data.job : {}, loading: data ? data.loading : true, + application: data ? data.application : null, }; } diff --git a/src/shared/containers/Gigs/RecruitCRMJobs.jsx b/src/shared/containers/Gigs/RecruitCRMJobs.jsx index 59b2973ba8..a806d1fa07 100644 --- a/src/shared/containers/Gigs/RecruitCRMJobs.jsx +++ b/src/shared/containers/Gigs/RecruitCRMJobs.jsx @@ -109,7 +109,12 @@ class RecruitCRMJobsContainer extends React.Component { } = this.state; if (loading) { - return ; + return ( + + ; +

Searching our database for the best gigs…

+
+ ); } let jobsToDisplay = jobs; @@ -139,6 +144,9 @@ class RecruitCRMJobsContainer extends React.Component { if (skills && skills.value && skills.value.toLowerCase().includes(_term)) return true; // location if (job.country.toLowerCase().includes(_term)) return true; + // duration + const duration = _.find(job.custom_fields, ['field_name', 'Duration']); + if (duration && duration.value && duration.value.toLowerCase().includes(_term)) return true; // no match return false; }); diff --git a/src/shared/containers/Gigs/jobLisingStyles.scss b/src/shared/containers/Gigs/jobLisingStyles.scss index 1d909ee9c1..923c4fe8fb 100644 --- a/src/shared/containers/Gigs/jobLisingStyles.scss +++ b/src/shared/containers/Gigs/jobLisingStyles.scss @@ -1,5 +1,13 @@ @import "~styles/mixins"; +.loading-text { + font-family: Roboto, sans-serif; + font-size: 24px; + line-height: 26px; + color: #2a2a2a; + text-align: center; +} + .container { max-width: $screen-lg; margin: auto; diff --git a/src/shared/containers/Gigs/techSkills.js b/src/shared/containers/Gigs/techSkills.js new file mode 100644 index 0000000000..e9983401b8 --- /dev/null +++ b/src/shared/containers/Gigs/techSkills.js @@ -0,0 +1 @@ +export default ['J2EE', 'Java', 'JavaBean', 'EJB', 'JSP', 'Servlet', 'Applet', 'Java Application', 'JMS', 'Web Services', '.NET', 'VB', 'C++', 'COM', 'XML', 'XSL', 'HTML', 'HTTP', 'C#', 'VB.NET', 'JSF', 'J2ME', 'MIDP 2.0', 'XUL', 'JavaScript', 'IIS', 'Oracle 10g', 'Oracle 9i', 'SQL Server', 'COM+', 'Windows Workflow Foundation', 'Windows Communication Foundation', 'XAML', 'Microsoft SilverLight', 'Spring', 'Dojo', 'AJAX', 'Struts', 'CSS', 'WPF', 'ClickOnce', 'PostgreSQL', '.NET System.Addins', 'MSMQ', 'SWT', 'Eclipse Plugin', 'JSON', 'JUnit', 'Swing', 'JPA', 'Hibernate', 'LDAP', 'Active Directory', 'C', 'MySQL', 'Apache Derby', 'JBoss Seam', 'Fortran', 'PHP', 'Ruby', 'Objective C', 'Ruby on Rails', 'Python', 'UML', 'Word/Rich Text', 'SQL', 'PL/SQL', 'SSIS', 'Windows Server', 'Sharepoint 3.0', 'COBOL', 'IBM WebSphere DataPower', 'Perl', 'SAP', 'Siebel', 'Xcode', 'Blackberry SDK', 'BizTalk', 'Sencha Touch 2', 'HTML5', 'PhoneGap', 'Android', 'Node.js', 'Drools', 'Salesforce', 'iBATIS/MyBatis', 'Apex', 'Visualforce', 'jQuery', 'Angular.js (1.0)', 'MongoDB', 'Twitter Bootstrap', 'API', 'Redis', 'Backbone.js', 'Google App Engine', 'Google API', 'Chatter', 'CoffeeScript', 'iOS', 'Databasedotcom', 'Elasticsearch', 'Express', 'Force.com Sites', 'Go', 'Groovy', 'NoSQL', 'Photoshop', 'Play! Framework', 'REST', 'SFDC Mobile', 'OSX', 'Bootstrap', 'Other', 'HPE Haven OnDemand', 'jQuery Mobile', 'Titanium', 'Swift', 'XMPP', 'Jabber', 'Cisco', 'Data Science', 'Lightning', 'R', 'Vertica', 'tvOS', 'Ant', 'Maven', 'Gradle', 'Spark', 'Docker', 'ReactJS', 'Grommet', 'Matlab', 'Predix', 'Ionic', 'IBM Cognitive', 'IBM Watson', 'AI', 'Cognitive', 'Angular 2+', 'Blockchain', 'ASP.NET Core', 'Continuous Integration', 'AWS', 'Beanstalk', 'Box', 'Brivo Labs', 'Actian X – Hybrid Database', 'Cloud Foundry', 'CloudFactor', 'DocuSign', 'Facebook', 'FinancialForce', 'Actian DataConnect – Data Integration', 'Gaming', 'Google', 'Heroku', 'Actian Vector – SMP Analytics Database', 'Actian Versant – NoSQL Object Database', 'IBM Cloud', 'Actian Ingres – Relational Database', 'Linux', 'MESH01', 'Microsoft Azure', 'Mobile', 'Actian OpenRoad - 4GL', 'Actian PSQL – Embeddable Database', 'Actian Data Management', 'Salesforce.com', 'Smartsheet', 'Twilio', 'Wordpress', 'Hyperledger Fabric', 'Actian Data Integration', 'Actian Data Analytics', 'QA', 'Marvel - Design', 'Ethereum', 'Solidity', 'Xamarin', 'MarkLogic', 'UI Prototype', 'Frontend', 'Quorum', 'R3 Corda', 'Hashgraph', 'Hyperledger Sawtooth', 'Multichain', 'ipfs', 'ActionScript', 'ADO.NET', 'Advanced Math', 'Akka', 'Algorithm', 'Apache Camel', 'Apache Cordova', 'Apache Flume', 'Apache Kafka', 'Appcelerator', 'Appium', 'Apple', 'Apple HIG', 'Atom', 'Axure', 'Bash', 'BigQuery', 'Bower', 'Branding', 'Brute Force', 'Calabash', 'CartoDB', 'Cassandra', 'Castor', 'Chrome', 'Class', 'Clojure', 'Cocoa', 'Codeigniter', 'Commerce Server 2009', 'Compression', 'Concept Design', 'Couchbase', 'Custom Tag', 'Customer Experience (Cx)', 'D3.JS', 'Database', 'Dc.js', 'Django', 'Doctrine', 'Dropwizard', 'Drupal', 'Dynamic Programming', 'EC2', 'Ember.js', 'Entity-Framework', 'Espruino', 'Excel', 'F#', 'File', 'Flash', 'Flex', 'Flight.js', 'Flux', 'Forms', 'Foundation', 'Function', 'Geometry', 'Gimp', 'Git', 'Github', 'Gitlab', 'Google Dart', 'Google-Maps', 'Graph Theory', 'Graphic Design', 'Greedy', 'Gremlin', 'Grunt.js', 'Gulp', 'Hadoop', 'HAML', 'Haskell', 'IBM AiX', 'IBM COGNOS', 'IBM DB2', 'IBM Design', 'IBM Lotus Domino', 'IBM Lotus Notes', 'IBM Pl/1', 'IBM Rational Application Developer', 'IBM Rational Data Architect', 'IBM Rational Software Architect', 'IBM Rational Team Concert', 'IBM REXX', 'IBM Websphere Application Server', 'IBM WebSphere DataStage', 'IBM Websphere Message Broker', 'IBM WebSphere MQ', 'IBM WebSphere Portal', 'IDOL OnDemand', 'Illustrator', 'Image', 'InDesign', 'IndexedDB', 'Infographic', 'Information Architecture (IA)', 'Inkscape', 'Interaction Design (Ixd)', 'InVision', 'JDBC', 'Jekyll', 'JetBrains', 'Jface', 'Jruby', 'Knockout', 'Kraken.js', 'Laravel', 'Leaflet.js', 'Less', 'Linq', 'List', 'Logo', 'Lua', 'Machine Learning', 'MariaDB', 'Math', 'Meteor.js', 'Mobile Design', 'Moodle', 'Mozilla', 'Multithreading', 'Neo4J', 'NGINX', 'Nodewebkit', 'Npm', 'Oauth', 'Om', 'OmniGraffle', 'Openam', 'Openstack', 'OSGi', 'Performance', 'Phantomjs', 'PowerShell', 'Print', 'Product Design', 'Protractor', 'Q & Bluebird', 'Qt', 'Recursion', 'Redhat', 'Regex', 'Remoting', 'Require.js', 'Responsive Design', 'Responsive Web Design', 'RMI', 'S3', 'safari', 'SASS', 'Scala', 'SCSS', 'Search', 'Security', 'Shell', 'Simple Math', 'Simple Search', 'Simulation', 'Sinatra', 'Sketch', 'Sockets', 'Sorting', 'Sqlite', 'String', 'String Manipulation', 'String Parsing', 'Swagger', 'Sympfony', 'Transact-SQL', 'Travis', 'Trello', 'TypeScript', 'Ubuntu', 'UITableView', 'User Experience (Ux)', 'User Interface (Ui)', 'Validation', 'VBA', 'Vert.X', 'vim', 'Visual-Studio', 'WCF', 'Website Design', 'Windows', 'Windows Phone', 'Winforms', 'Winforms Controls', 'Yii', 'Zend framework', 'Zepto.js', 'Zipkin', 'MapReduce', 'Big data', 'Hadoop Admin', 'Tableau', 'Ab Initio', 'Power BI', 'Informatica Big Data', 'Informatica IDQ / IDE', 'QlikView', 'Talend', 'BackOffice Associates', 'Webmethods', 'TIBCO', 'IBM integration', 'Dell Boomi', 'Angular 4', 'Microservices', 'Apigee', 'ASP.NET MVC 5', 'ASP.NET Web API', 'CA', 'DevOps', 'Google Cloud', 'LoadRunner', 'Api Connect', 'Integrator', 'IBM Cloud Private', 'IBM Integration Bus', 'Websphere Liberty', 'JMeter', 'Kubernetes', 'Microsoft Dynamics', 'Office365', 'Microsoft Exchange', 'Open Source', 'OpenShift', 'Selenium', 'Sharepoint 2016', 'Aris', 'Cumulocity', 'Tosca', 'Business-to-business', 'FuseESB', 'WSO2 ESB', 'Mainframe', 'JBehave', 'Splunk', 'Oracle EBS', 'Hybris', 'Pega', 'Relational Algebra', 'Outliers', 'Smoothing', 'Computer Vision', 'Object Detection', 'Pattern Recognition', 'Pattern Matching', 'Video Tracking', 'Pose Estimation', 'NLP', 'Speech Recognition', 'NLG', 'DSS', 'Teaching systems', 'Deep Learning', 'Robotics', 'Recommendation System', 'IOT', 'Expert Systems', 'Genetic Algorithm', 'Evolutionary Algorithm', 'Image Processing', 'OCR', 'Event Detection', '3D Reconstruction', 'Gaussian Filtering', 'Median Filtering', 'Imputation', 'Random Sampling', 'Montecarlo', 'Statistical distributions', 'Regression', 'NLU', 'Dimensionality Reduction', 'Factor Analysis', 'K Means', 'Canopy Clustering', 'TF IDR', 'Feature Hashing', 'Wrapper Methods', 'Sensitivity Analysis', 'Self organizing Maps', 'Deduplication', 'Normalization', 'Format Conversion', 'FFT', 'DWT', 'Coordinate Transformation', 'Statistical Count', 'Standard Deviation', 'Mean', 'Range', 'Boxplot', 'Scatter Plots', 'Distributive Aggregation', 'Distribution fitting', 'Baseball Card Aggregation', 'Enrichment', 'Clustering', 'Baseball Card Aggregation', 'Enrichment', 'Annotation', 'Clustering', 'X Means', 'Canopy', 'Apriori', 'Topic Modeling', 'Non-Elliptical Clustering', 'Fractals', 'Database Scan', 'Guassian Mixture Model', 'K-Means', 'Tree', 'GLM', 'Regression', 'LASSO', 'Elastic Net', 'Stepwise regression', 'T Testing', 'ANOVA', 'Classification', 'Bayesian Network', 'Neural Network', 'Random Forest', 'Decision Tree', 'Nearest Neighbors', 'Naivebayes', 'Hidden Markov Model', 'SVM', 'Nearest Neighbors', 'Collaborative filtering', 'Content-based methods', 'Graph-based methods', 'Logical reasoning', 'Optimization', 'Stochastic', 'Stimulated Annealing', 'Gradient Dissent', 'Linear Programming', 'Integer Programming', 'Non-Linear Optimization', 'Markov Models', 'Agent Based Modelling', 'Montecarlo', 'System Dynamics', 'Activity Based Simulation', 'ODE', 'PDE', 'Fuzzy Logic', 'Deep Fake', 'Algorithimic Trading', 'Clinical Trials', 'Medical Records', 'Diagnostics', 'Angular 8', 'React Native', 'Flutter testing', 'Dummy Challenge', 'Domino', 'Bamboo', 'Jenkins', 'Puppet', 'Ansible', 'Terraform', 'Landslide', 'XSLT', 'BizTalk', 'Three.js', 'Unity 3D', 'CI/CD', 'Databricks', 'Angular', 'Azure', 'Spring Boot', 'Devops', 'Kotlin']; diff --git a/src/shared/containers/GigsPages.jsx b/src/shared/containers/GigsPages.jsx index ac098eef89..1c83955d9a 100644 --- a/src/shared/containers/GigsPages.jsx +++ b/src/shared/containers/GigsPages.jsx @@ -14,6 +14,7 @@ import { Helmet } from 'react-helmet'; export default function GigsPagesContainer(props) { const { match } = props; const { id } = match.params; + const isApply = `${config.GIGS_PAGES_PATH}/${id}/apply` === match.url; return (
@@ -32,6 +33,7 @@ window._chatlio = window._chatlio||[]; id ? ( ) : ( + ( diff --git a/src/shared/services/recruitCRM.js b/src/shared/services/recruitCRM.js index 0c124ef6ee..e20cccb5f4 100644 --- a/src/shared/services/recruitCRM.js +++ b/src/shared/services/recruitCRM.js @@ -1,6 +1,7 @@ import fetch from 'isomorphic-fetch'; import { logger } from 'topcoder-react-lib'; import qs from 'qs'; +import _ from 'lodash'; const PROXY_ENDPOINT = '/api/recruit'; @@ -9,7 +10,7 @@ export default class Service { /** * Get jobs by query - * @param {*} query The request query + * @param {object} query The request query */ async getJobs(query) { const res = await fetch(`${this.baseUrl}/jobs/search?${qs.stringify(query)}`); @@ -35,7 +36,7 @@ export default class Service { /** * Get all jobs - * @param {*} query The request query + * @param {object} query The request query */ async getAllJobs(query) { const res = await fetch(`${this.baseUrl}/jobs?${qs.stringify(query)}`); @@ -45,4 +46,25 @@ export default class Service { } return res.json(); } + + /** + * applyForJob for candidate + * @param {string} id The job id to apply to + * @param {object} payload The apply payload + */ + async applyForJob(id, payload) { + const { resume } = payload; + const data = new FormData(); + data.append('resume', resume); + data.append('form', JSON.stringify(_.omit(payload, 'resume'))); + const res = await fetch(`${this.baseUrl}/jobs/${id}/apply`, { + method: 'POST', + body: data, + }); + if (!res.ok) { + const error = new Error('Failed to apply for job'); + logger.error(error, res); + } + return res.json(); + } }