diff --git a/.eslintrc b/.eslintrc index 5c36732..a998e96 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,4 +1,12 @@ { - "extends": ["react-important-stuff", "plugin:prettier/recommended"], - "parser": "babel-eslint" + "extends": [ + "eslint:recommended", + "plugin:react/recommended", + "react-important-stuff", + "plugin:prettier/recommended" + ], + "parser": "babel-eslint", + "rules": { + "no-unused-vars": "warn" + } } diff --git a/README.md b/README.md index 2529e55..fbf648e 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ This is a [single-spa](https://single-spa.js.org/) example React microapp. ## Config -For available variables config which depend on the running environment (`APPENV=dev` or `APPENV=prod`), please refer to `config/development.js` and `config/production.js`. +For available variables config which depend on the running environment (`APPENV=dev` or `APPENV=prod`), please refer to `config/dev.js` and `config/prod.js`. For application constants which don't depend on the running environment use `src/constants/index.js`. @@ -59,14 +59,14 @@ Make sure you have [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli ## How to run Locally for Development -TaaS App is done using Single SPA micro-frontend architecture https://single-spa.js.org/. So to start it, we would also have to run Frame App and Navbar App. Here I would show the steps to run locally everything we need. +TaaS Admin App is done using Single SPA micro-frontend architecture https://single-spa.js.org/. So to start it, we would also have to run Frame App and Navbar App. Here I would show the steps to run locally everything we need. ### Local Authentication First of all, to authenticate locally we have to run a local authentication service. - Clone this repository into `taas-admin-app`. -- Inside the folder `taas-admin-app/local/auth-local` run `npm start`. +- Inside the folder `taas-admin-app/auth-local` run `npm start`. - You would need npm 5+ for it. This would start a local sever on port 5000 which could be used for local Authentication. ### Local Domain @@ -115,7 +115,7 @@ Some config files are using domain `local.topcoder-dev.com`. You can change it t cd micro-frontends-navbar-app ``` - Update in file `micro-frontends-navbar-app/blob/dev/config/dev.js` values for `ACCOUNTS_APP_CONNECTOR` and `AUTH` to `http://localhost:5000` so Navbar app which handles authentication uses our local Authentication service. + Update in file `micro-frontends-navbar-app/config/dev.js` values for `ACCOUNTS_APP_CONNECTOR` and `AUTH` to `http://localhost:5000` so Navbar app which handles authentication uses our local Authentication service. ```sh # inside folder "micro-frontends-navbar-app" run: @@ -128,31 +128,31 @@ Some config files are using domain `local.topcoder-dev.com`. You can change it t # this host navbar app as http://localhost:3001/navbar/topcoder-micro-frontends-navbar-app.js ``` -3. Run **TaaS** micro-app: +3. Run **TaaS Admin** micro-app: ```sh - # inside folder "taas-app" run: + # inside folder "taas-admin-app" run: nvm use # or make sure to use Node 10 npm i # to install dependencies npm run dev - # this host TaaS App as http://localhost:8501/taas-app/topcoder-micro-frontends-teams.js + # this hosts TaaS Admin App as http://localhost:8502/taas-admin-app/topcoder-micro-frontends-taas-admin-app.js ``` -4. Now we have to update the `micro-frontends-frame` app to show our local version of TaaS App, instead of remote one. Update file `micro-frontends-frame/config/micro-frontends-config-local.json`: +4. Now we have to update the `micro-frontends-frame` app to show our local version of TaaS Admin App, instead of remote one. Update file `micro-frontends-frame/config/micro-frontends-config-local.json`: ```js // replace line - "@topcoder/micro-frontends-teams": "https://platform.topcoder-dev.com/taas-app/topcoder-micro-frontends-teams.js", + "@topcoder/micro-frontends-taas-admin-app": "https://platform.topcoder-dev.com/taas-admin-app/topcoder-micro-frontends-taas-admin-app.js", // with line: - "@topcoder/micro-frontends-teams": "http://localhost:8501/taas-app/topcoder-micro-frontends-teams.js", + "@topcoder/micro-frontends-taas-admin-app": "http://localhost:8502/taas-admin-app/topcoder-micro-frontends-taas-admin-app.js", ``` -- Now open in the browser http://localhost:8080/taas/myteams. +- Now open in the browser http://localhost:8080/taas-admin. - If you are not logged-in yet, you should be redirected to the login page. -- If you cannot see the application and redirect doesn't happen, make sure that file "http://local.topcoder-dev.com:8501/taas-app/topcoder-micro-frontends-teams.js" is loaded successfully in the Network tab. +- If you cannot see the application and redirect doesn't happen, make sure that file "http://local.topcoder-dev.com:8502/taas-admin-app/topcoder-micro-frontends-taas-admin-app.js" is loaded successfully in the Network tab. Congratulations, you successfully run the project. If you had some issue, please, try to go through README of https://github.com/topcoder-platform/micro-frontends-frame and https://github.com/topcoder-platform/micro-frontends-navbar-app. diff --git a/babel.config.js b/babel.config.js index 676353c..0ae0dd5 100644 --- a/babel.config.js +++ b/babel.config.js @@ -46,6 +46,7 @@ module.exports = function (api) { // "module-resolver", // { // alias: { + // assets: "./src/assets", // styles: "./src/styles", // components: "./src/components", // hooks: "./src/hooks", diff --git a/config/dev.js b/config/dev.js index 2cba008..04cc1f2 100644 --- a/config/dev.js +++ b/config/dev.js @@ -1,16 +1,6 @@ module.exports = { - /** - * URL of Topcoder Community Website - */ - TOPCODER_COMMUNITY_WEBSITE_URL: "https://topcoder-dev.com", - - /** - * URL of Topcoder Connect Website - */ - CONNECT_WEBSITE_URL: "https://connect.topcoder-dev.com", - API: { V5: "https://api.topcoder-dev.com/v5", - V3: "https://api.topcoder-dev.com/v3", }, + PLATFORM_WEBSITE_URL: "https://platform.topcoder-dev.com", }; diff --git a/config/index.js b/config/index.js index 49f591b..8511e16 100644 --- a/config/index.js +++ b/config/index.js @@ -1,14 +1,10 @@ /* global process */ module.exports = (() => { - const env = process.env.APPENV || "dev"; + const appEnv = process.env.APPENV === "prod" ? "prod" : "dev"; - console.log(`APPENV: "${env}"`); + // eslint-disable-next-line no-console + console.log(`APPENV: "${appEnv}"`); - // for security reason don't let to require any arbitrary file defined in process.env - if (["prod", "dev"].indexOf(env) < 0) { - return require("./dev"); - } - - return require("./" + env); + return require(`./${appEnv}`); })(); diff --git a/config/prod.js b/config/prod.js index 21cdc37..7d0f7c4 100644 --- a/config/prod.js +++ b/config/prod.js @@ -1,16 +1,6 @@ module.exports = { - /** - * URL of Topcoder Community Website - */ - TOPCODER_COMMUNITY_WEBSITE_URL: "https://topcoder.com", - - /** - * URL of Topcoder Connect Website - */ - CONNECT_WEBSITE_URL: "https://connect.topcoder.com", - API: { V5: "https://api.topcoder.com/v5", - V3: "https://api.topcoder.com/v3", }, + PLATFORM_WEBSITE_URL: "https://platform.topcoder.com", }; diff --git a/package-lock.json b/package-lock.json index 6634b49..3cfcece 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3822,6 +3822,18 @@ "es-abstract": "^1.17.4" } }, + "array.prototype.flatmap": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.4.tgz", + "integrity": "sha512-r9Z0zYoxqHz60vvQbWEdXIEtCwHF0yxaWfno9qzXeNHvfyl3BZqygmGzb84dsubyaXLH4husF+NFgMSdpZhk2Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.1", + "function-bind": "^1.1.1" + } + }, "arrify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", @@ -6419,6 +6431,47 @@ "prettier-linter-helpers": "^1.0.0" } }, + "eslint-plugin-react": { + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.23.2.tgz", + "integrity": "sha512-AfjgFQB+nYszudkxRkTFu0UR1zEQig0ArVMPloKhxwlwkzaw/fBiH0QWcBBhZONlXqQC51+nfqFrkn4EzHcGBw==", + "dev": true, + "requires": { + "array-includes": "^3.1.3", + "array.prototype.flatmap": "^1.2.4", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.0.4", + "object.entries": "^1.1.3", + "object.fromentries": "^2.0.4", + "object.values": "^1.1.3", + "prop-types": "^15.7.2", + "resolve": "^2.0.0-next.3", + "string.prototype.matchall": "^4.0.4" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "resolve": { + "version": "2.0.0-next.3", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.3.tgz", + "integrity": "sha512-W8LucSynKUIDu9ylraa7ueVZ7hc0uAgJBxVsQSKOXOyle8a93qXhcz+XAXZ8bIq2d6i4Ehddn6Evt+0/UwKk6Q==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + } + } + }, "eslint-plugin-react-hooks": { "version": "2.5.1", "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.5.1.tgz", @@ -8425,6 +8478,17 @@ "ipaddr.js": "^1.9.0" } }, + "internal-slot": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", + "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, "interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", @@ -13172,6 +13236,18 @@ "has": "^1.0.3" } }, + "object.fromentries": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.4.tgz", + "integrity": "sha512-EsFBshs5RUUpQEY1D4q/m59kMfz4YJvxuNCJcv/jWwOJr34EaVnG11ZrZa0UHB3wnzV1wx8m58T4hQL8IuNXlQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2", + "has": "^1.0.3" + } + }, "object.getownpropertydescriptors": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", @@ -15393,6 +15469,17 @@ "dev": true, "optional": true }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", @@ -15970,6 +16057,48 @@ "strip-ansi": "^5.1.0" } }, + "string.prototype.matchall": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.5.tgz", + "integrity": "sha512-Z5ZaXO0svs0M2xd/6By3qpeKpLKd9mO4v4q3oMEQrk8Ck4xOD5d5XeBOOjGrmVZZ/AHB1S0CgG4N5r1G9N3E2Q==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.2", + "get-intrinsic": "^1.1.1", + "has-symbols": "^1.0.2", + "internal-slot": "^1.0.3", + "regexp.prototype.flags": "^1.3.1", + "side-channel": "^1.0.4" + }, + "dependencies": { + "es-abstract": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", + "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.3", + "is-string": "^1.0.6", + "object-inspect": "^1.10.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + } + } + } + }, "string.prototype.trimend": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", @@ -16744,6 +16873,17 @@ } } }, + "url-loader": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-4.1.1.tgz", + "integrity": "sha512-3BTV812+AVHHOJQO8O5MkWgZ5aosP7GnROJwvzLS9hWDj00lZ6Z0wNak423Lp9PBZN05N+Jk/N5Si8jRAlGyWA==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "mime-types": "^2.1.27", + "schema-utils": "^3.0.0" + } + }, "url-parse": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", diff --git a/package.json b/package.json index f236d9b..00665f0 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "eslint-config-prettier": "^6.7.0", "eslint-config-react-important-stuff": "^2.0.0", "eslint-plugin-prettier": "^3.1.1", + "eslint-plugin-react": "^7.23.2", "file-loader": "^6.2.0", "identity-obj-proxy": "^3.0.0", "jest": "^25.2.7", @@ -93,6 +94,7 @@ "single-spa-react": "^2.14.0", "style-loader": "^2.0.0", "systemjs-webpack-interop": "^2.1.2", + "url-loader": "^4.1.1", "webpack": "^4.41.2", "webpack-cli": "^3.3.10", "webpack-config-single-spa-react": "^1.0.3", diff --git a/server.js b/server.js index ff83f95..f0dfc56 100644 --- a/server.js +++ b/server.js @@ -22,7 +22,7 @@ app.get("/", (req, res) => { res.send("alive"); }); -const PORT = process.env.PORT || 8501; +const PORT = process.env.PORT || 8502; app.listen(PORT, () => { console.log(`App is hosted on port ${PORT}.`); // eslint-disable-line no-console }); diff --git a/src/assets/images/icon-arrow-down-small.svg b/src/assets/images/icon-arrow-down-small.svg index 02b6b6c..795e5c8 100644 --- a/src/assets/images/icon-arrow-down-small.svg +++ b/src/assets/images/icon-arrow-down-small.svg @@ -1,7 +1,19 @@ - - - + + diff --git a/src/assets/images/icon-arrow-down.svg b/src/assets/images/icon-arrow-down.svg index f1280a4..5618a0a 100644 --- a/src/assets/images/icon-arrow-down.svg +++ b/src/assets/images/icon-arrow-down.svg @@ -1,10 +1,22 @@ - - - + + C6.7934,8.2903,6.7924,8.2893,6.7914,8.2883z" + /> diff --git a/src/assets/images/icon-arrow-left-green.png b/src/assets/images/icon-arrow-left-green.png deleted file mode 100644 index 87d176d..0000000 Binary files a/src/assets/images/icon-arrow-left-green.png and /dev/null differ diff --git a/src/assets/images/icon-arrow-left.svg b/src/assets/images/icon-arrow-left.svg index abfb337..3f7c835 100644 --- a/src/assets/images/icon-arrow-left.svg +++ b/src/assets/images/icon-arrow-left.svg @@ -1,7 +1,19 @@ - - - + + diff --git a/src/assets/images/icon-arrow-right-green.png b/src/assets/images/icon-arrow-right-green.png deleted file mode 100644 index dfa18cc..0000000 Binary files a/src/assets/images/icon-arrow-right-green.png and /dev/null differ diff --git a/src/assets/images/icon-arrow-right.svg b/src/assets/images/icon-arrow-right.svg index 226d94e..8f6f601 100644 --- a/src/assets/images/icon-arrow-right.svg +++ b/src/assets/images/icon-arrow-right.svg @@ -1,7 +1,19 @@ - - - + + diff --git a/src/assets/images/icon-magnifier.svg b/src/assets/images/icon-magnifier.svg index 4660b6d..06ae4d6 100644 --- a/src/assets/images/icon-magnifier.svg +++ b/src/assets/images/icon-magnifier.svg @@ -1,6 +1,5 @@ - 7A98E5AD-FECB-4DD2-9CB2-207FDF43A1DA @@ -18,4 +17,4 @@ - \ No newline at end of file + diff --git a/src/assets/images/icon-menu-item-feelancers.svg b/src/assets/images/icon-menu-item-feelancers.svg index c52005a..76e32d3 100644 --- a/src/assets/images/icon-menu-item-feelancers.svg +++ b/src/assets/images/icon-menu-item-feelancers.svg @@ -1,23 +1,49 @@ - - - + + - + - + - + - + + c0-2.4853-2.0147-4.5-4.5-4.5s-4.5,2.0147-4.5,4.5V15H9c0.3856-0.0013,0.7088,0.2912,0.746,0.675l0.682,6.825H13.571z" + /> diff --git a/src/assets/images/icon-menu-item-workperiods.svg b/src/assets/images/icon-menu-item-workperiods.svg index 045bcb3..4078d17 100644 --- a/src/assets/images/icon-menu-item-workperiods.svg +++ b/src/assets/images/icon-menu-item-workperiods.svg @@ -1,11 +1,66 @@ - - + - - - - + c-1.2887,0-2.3333-1.0447-2.3333-2.3333V5.9444C1.6667,4.6558,2.7113,3.6111,4,3.6111z" + /> + + + + diff --git a/src/assets/images/icon-tick-white-bg-green.png b/src/assets/images/icon-tick-white-bg-green.png deleted file mode 100644 index 82c777b..0000000 Binary files a/src/assets/images/icon-tick-white-bg-green.png and /dev/null differ diff --git a/src/assets/images/icon-tick-white.svg b/src/assets/images/icon-tick-white.svg index 448b389..35d4235 100644 --- a/src/assets/images/icon-tick-white.svg +++ b/src/assets/images/icon-tick-white.svg @@ -1,6 +1,20 @@ - - + + diff --git a/src/components/Button/index.jsx b/src/components/Button/index.jsx index 85f4a04..18f73b2 100644 --- a/src/components/Button/index.jsx +++ b/src/components/Button/index.jsx @@ -9,7 +9,7 @@ import styles from "./styles.module.scss"; * @param {Object} props component properties * @param {Object} props.children button text * @param {string} [props.className] class name added to root element - * @param {'primary'|'primary-dark'} [props.color] button color + * @param {'primary'|'primary-dark'|'primary-light'} [props.color] button color * @param {boolean} [props.isSelected] if button is selected * @param {string} [props.name] button name * @param {(e: any) => void} props.onClick function called when button is clicked @@ -58,6 +58,7 @@ Button.propTypes = { children: PT.node, className: PT.string, color: PT.oneOf(["primary"]), + isSelected: PT.bool, name: PT.string, onClick: PT.func, size: PT.oneOf(["medium"]), diff --git a/src/components/Button/styles.module.scss b/src/components/Button/styles.module.scss index c758bf3..14164c5 100644 --- a/src/components/Button/styles.module.scss +++ b/src/components/Button/styles.module.scss @@ -43,7 +43,12 @@ &.primary { border-color: $primary-color; - color: $primary-color; + color: $primary-text-color; + } + + &.primary-light { + border-color: $primary-light-color; + color: $primary-light-text-color; } &.primary-dark { @@ -62,6 +67,11 @@ background-color: $primary-color; } + &.primary-light { + border-color: $primary-light-color; + background-color: $primary-light-color; + } + &.primary-dark { border-color: $primary-dark-color; background-color: $primary-dark-color; diff --git a/src/components/Checkbox/index.jsx b/src/components/Checkbox/index.jsx index 50e55a2..28d00df 100644 --- a/src/components/Checkbox/index.jsx +++ b/src/components/Checkbox/index.jsx @@ -48,7 +48,9 @@ const Checkbox = ({ Checkbox.propTypes = { checked: PT.bool, + className: PT.string, name: PT.string.isRequired, + size: PT.oneOf(["medium", "small"]), onChange: PT.func.isRequired, option: PT.shape({ value: PT.string.isRequired, diff --git a/src/components/ContentBlock/styles.module.scss b/src/components/ContentBlock/styles.module.scss index 7cd756f..7b15d7f 100644 --- a/src/components/ContentBlock/styles.module.scss +++ b/src/components/ContentBlock/styles.module.scss @@ -1,5 +1,7 @@ @import "styles/mixins"; .container { + margin-bottom: 50px; + border-radius: 8px; background-color: #fff; } diff --git a/src/components/IconWrapper/index.jsx b/src/components/IconWrapper/index.jsx index 1e7be4f..6b99cb3 100644 --- a/src/components/IconWrapper/index.jsx +++ b/src/components/IconWrapper/index.jsx @@ -1,4 +1,5 @@ import React, { memo } from "react"; +import PT from "prop-types"; import cn from "classnames"; import styles from "./styles.module.scss"; @@ -15,4 +16,8 @@ const IconWrapper = (props) => ( ); +IconWrapper.propTypes = { + className: PT.string, +}; + export default memo(IconWrapper); diff --git a/src/components/Icons/ArrowDown/index.jsx b/src/components/Icons/ArrowDown/index.jsx index 98b3060..4afc8ff 100644 --- a/src/components/Icons/ArrowDown/index.jsx +++ b/src/components/Icons/ArrowDown/index.jsx @@ -2,16 +2,20 @@ import React from "react"; import PT from "prop-types"; import cn from "classnames"; import IconWrapper from "components/IconWrapper"; +import IconArrowDown from "../../../assets/images/icon-arrow-down.svg"; import styles from "./styles.module.scss"; /** + * Displays a wide dark green arrow pointing down. * * @param {Object} props component props * @param {string} [props.className] class name added to root element * @returns {JSX.Element} */ const ArrowDown = ({ className }) => ( - {jsx} + + + ); ArrowDown.propTypes = { @@ -19,28 +23,3 @@ ArrowDown.propTypes = { }; export default ArrowDown; - -// This JSX will never change so it's alright to create it only once. -const jsx = ( - - - -); diff --git a/src/components/Icons/ArrowLeft/index.jsx b/src/components/Icons/ArrowLeft/index.jsx index add5974..8a14d8b 100644 --- a/src/components/Icons/ArrowLeft/index.jsx +++ b/src/components/Icons/ArrowLeft/index.jsx @@ -2,16 +2,20 @@ import React from "react"; import PT from "prop-types"; import cn from "classnames"; import IconWrapper from "components/IconWrapper"; +import IconArrowLeft from "../../../assets/images/icon-arrow-left.svg"; import styles from "./styles.module.scss"; /** + * Displays a green arrow pointing to the left. * * @param {Object} props component props * @param {string} [props.className] class name added to root element * @returns {JSX.Element} */ const ArrowLeft = ({ className }) => ( - {jsx} + + + ); ArrowLeft.propTypes = { @@ -19,25 +23,3 @@ ArrowLeft.propTypes = { }; export default ArrowLeft; - -// This JSX will never change so it's alright to create it only once. -const jsx = ( - - - -); diff --git a/src/components/Icons/ArrowRight/index.jsx b/src/components/Icons/ArrowRight/index.jsx index c3e5f19..0001253 100644 --- a/src/components/Icons/ArrowRight/index.jsx +++ b/src/components/Icons/ArrowRight/index.jsx @@ -2,16 +2,20 @@ import React from "react"; import PT from "prop-types"; import cn from "classnames"; import IconWrapper from "components/IconWrapper"; +import IconArrowRight from "../../../assets/images/icon-arrow-right.svg"; import styles from "./styles.module.scss"; /** + * Displays a green arrow pointing to the right. * * @param {Object} props component props * @param {string} [props.className] class name added to root element * @returns {JSX.Element} */ const ArrowRight = ({ className }) => ( - {jsx} + + + ); ArrowRight.propTypes = { @@ -19,25 +23,3 @@ ArrowRight.propTypes = { }; export default ArrowRight; - -// This JSX will never change so it's alright to create it only once. -const jsx = ( - - - -); diff --git a/src/components/Icons/ArrowSmall/index.jsx b/src/components/Icons/ArrowSmall/index.jsx index dc9d633..6a41ab2 100644 --- a/src/components/Icons/ArrowSmall/index.jsx +++ b/src/components/Icons/ArrowSmall/index.jsx @@ -2,14 +2,15 @@ import React from "react"; import PT from "prop-types"; import cn from "classnames"; import IconWrapper from "components/IconWrapper"; +import IconArrowDownSmall from "../../../assets/images/icon-arrow-down-small.svg"; import styles from "./styles.module.scss"; /** - * Displays small down-arrow. + * Displays a small narrow arrow pointing down or up. * * @param {Object} props component props * @param {string} [props.className] class name added to root element - * @param {() => void} props.onClick click handler + * @param {() => void} [props.onClick] click handler * @param {boolean} props.isActive whether the icon is in active state * @param {'up'|'down'} [props.direction] arrow direction * @returns {JSX.Element} @@ -21,33 +22,15 @@ const ArrowSmall = ({ className, onClick, isActive, direction = "down" }) => ( [styles.isActive]: isActive, })} > - {jsx} + ); ArrowSmall.propTypes = { className: PT.string, + isActive: PT.bool.isRequired, + direction: PT.oneOf(["down", "up"]), + onClick: PT.func, }; export default ArrowSmall; - -const jsx = ( - - - -); diff --git a/src/components/Icons/ArrowSmall/styles.module.scss b/src/components/Icons/ArrowSmall/styles.module.scss index df32d79..4751dd2 100644 --- a/src/components/Icons/ArrowSmall/styles.module.scss +++ b/src/components/Icons/ArrowSmall/styles.module.scss @@ -4,7 +4,9 @@ svg { display: block; width: 100%; + height: auto; + path, polygon { fill: #aaa; } @@ -12,6 +14,7 @@ &.isActive { svg { + path, polygon { fill: #000; } diff --git a/src/components/Icons/Freelancers/index.jsx b/src/components/Icons/Freelancers/index.jsx index 8880c39..62bc3a8 100644 --- a/src/components/Icons/Freelancers/index.jsx +++ b/src/components/Icons/Freelancers/index.jsx @@ -2,9 +2,11 @@ import React from "react"; import PT from "prop-types"; import cn from "classnames"; import IconWrapper from "components/IconWrapper"; +import IconFreelancers from "../../../assets/images/icon-menu-item-feelancers.svg"; import styles from "./styles.module.scss"; /** + * Displays a "people" icon used in navigation menu. * * @param {Object} props component props * @param {string} [props.className] class name added to root element @@ -15,7 +17,7 @@ const Freelancers = ({ className, isActive = false }) => ( - {jsx} + ); @@ -25,55 +27,3 @@ Freelancers.propTypes = { }; export default Freelancers; - -// This JSX will never change so it's alright to create it only once. -const jsx = ( - - - - - - - - -); diff --git a/src/components/Icons/WorkPeriods/index.jsx b/src/components/Icons/WorkPeriods/index.jsx index bb20c75..a29fe09 100644 --- a/src/components/Icons/WorkPeriods/index.jsx +++ b/src/components/Icons/WorkPeriods/index.jsx @@ -2,9 +2,11 @@ import React from "react"; import PT from "prop-types"; import cn from "classnames"; import IconWrapper from "components/IconWrapper"; +import IconWorkPeriods from "../../../assets/images/icon-menu-item-workperiods.svg"; import styles from "./styles.module.scss"; /** + * Displays a "calendar" icon used in navigation menu. * * @param {Object} props component props * @param {string} [props.className] class name added to root element @@ -15,7 +17,7 @@ const WorkPeriods = ({ className, isActive = false }) => ( - {jsx} + ); @@ -25,72 +27,3 @@ WorkPeriods.propTypes = { }; export default WorkPeriods; - -// This JSX will never change so it's alright to create it only once. -const jsx = ( - - - - - - - -); diff --git a/src/components/IntegerField/index.jsx b/src/components/IntegerField/index.jsx index 761eb46..2fde8c9 100644 --- a/src/components/IntegerField/index.jsx +++ b/src/components/IntegerField/index.jsx @@ -1,10 +1,10 @@ -import React, { useCallback } from "react"; +import React from "react"; import PT from "prop-types"; import cn from "classnames"; import styles from "./styles.module.scss"; /** - * Display input field for inputing numbers with plus and minus buttons. + * Displays input field for inputing integer numbers with plus and minus buttons. * * @param {Object} props component properties * @param {string} [props.className] class name to be added to root element @@ -39,6 +39,8 @@ const IntegerField = ({ IntegerField.propTypes = { className: PT.string, name: PT.string.isRequired, + maxValue: PT.number, + minValue: PT.string, onChange: PT.func.isRequired, value: PT.number.isRequired, }; diff --git a/src/components/LoadingIndicator/index.jsx b/src/components/LoadingIndicator/index.jsx index 694c20d..f51dbd0 100644 --- a/src/components/LoadingIndicator/index.jsx +++ b/src/components/LoadingIndicator/index.jsx @@ -1,13 +1,15 @@ -/** - * LoadingIndicator - * - * Optionally shows error. - */ import React from "react"; import get from "lodash/get"; import PT from "prop-types"; import styles from "./styles.module.scss"; +/** + * Displays "Loading..." message or an error. + * + * @param {Object} props component properties + * @param {Object} [props.error] error object + * @returns {JSX.Element} + */ const LoadingIndicator = ({ error }) => (
{!error diff --git a/src/components/NavLink/index.jsx b/src/components/NavLink/index.jsx index aed80fb..096c8bd 100644 --- a/src/components/NavLink/index.jsx +++ b/src/components/NavLink/index.jsx @@ -1,4 +1,4 @@ -import React, { useCallback } from "react"; +import React from "react"; import PT from "prop-types"; import cn from "classnames"; import { Link, useMatch } from "@reach/router"; @@ -30,10 +30,10 @@ const NavLink = ({ className, exact = false, icon: Icon, label, path }) => { ); }; -NavLink.ptopTypes = { +NavLink.propTypes = { className: PT.string, exact: PT.bool, - icon: PT.node.isRequired, + icon: PT.func.isRequired, label: PT.string.isRequired, path: PT.string.isRequired, }; diff --git a/src/components/Page/styles.module.scss b/src/components/Page/styles.module.scss index 8e72dc0..6fecdb9 100644 --- a/src/components/Page/styles.module.scss +++ b/src/components/Page/styles.module.scss @@ -1,11 +1,24 @@ @import "styles/mixins"; +@import "styles/variables"; .container { - flex: 1 0 auto; + position: relative; + z-index: 0; display: flex; flex-direction: column; + @include roboto-regular; + font-size: 14px; + line-height: (22/14); + color: $text-color; + background-color: $page-bg-color; @include desktop { flex-direction: row; } + + *, + *::before, + *::after { + box-sizing: border-box; + } } diff --git a/src/components/PageTitle/index.jsx b/src/components/PageTitle/index.jsx index 5ba2fdf..b2db721 100644 --- a/src/components/PageTitle/index.jsx +++ b/src/components/PageTitle/index.jsx @@ -17,7 +17,7 @@ const PageTitle = ({ className, text: title }) => ( PageTitle.propTypes = { className: PT.string, - title: PT.string.isRequired, + text: PT.string.isRequired, }; export default PageTitle; diff --git a/src/components/Pagination/index.jsx b/src/components/Pagination/index.jsx index 412d35a..a56209d 100644 --- a/src/components/Pagination/index.jsx +++ b/src/components/Pagination/index.jsx @@ -1,6 +1,8 @@ import React, { useCallback } from "react"; import PT from "prop-types"; import cn from "classnames"; +import IconArrowLeft from "components/Icons/ArrowLeft"; +import IconArrowRight from "components/Icons/ArrowRight"; import SelectField from "components/SelectField"; import styles from "./styles.module.scss"; import Button from "components/Button"; @@ -12,6 +14,8 @@ import Button from "components/Button"; * @param {string} [props.className] class name added to root element * @param {string} [props.pageSizeClassName] class name for page size select * @param {string} props.id id for input element + * @param {string} [props.label] label displayed to the left of page size dropdown + * @param {string} [props.pageSizeLabel] label displayed at the top of page size dropdown * @param {(v: number) => void} props.onPageNumberClick function called when page button is clicked * @param {() => void} props.onPageSizeChange function called when page size is changed * @param {Object} props.pageSizeOptions page size options object @@ -22,6 +26,8 @@ const Pagination = ({ className, pageSizeClassName, id, + label, + pageSizeLabel, onPageNumberClick, onPageSizeChange, pageSizeOptions, @@ -48,7 +54,7 @@ const Pagination = ({ size="small" value={pageNumber - 1} > - + Previous ); @@ -78,15 +84,16 @@ const Pagination = ({ value={pageNumber + 1} > Next - + ); } return (
- Records/Page + {label && {label}} { - const onInputChange = useCallback((event) => { - onChange(event.target.value); - }, []); + const onInputChange = useCallback( + (event) => { + onChange(event.target.value); + }, + [onChange] + ); return (
@@ -47,6 +50,8 @@ const SearchField = ({ SearchField.propTypes = { className: PT.string, + id: PT.string.isRequired, + size: PT.oneOf(["medium", "small"]), name: PT.string.isRequired, onChange: PT.func.isRequired, placeholder: PT.string, diff --git a/src/components/SelectField/index.jsx b/src/components/SelectField/index.jsx index 8e51d06..31d8587 100644 --- a/src/components/SelectField/index.jsx +++ b/src/components/SelectField/index.jsx @@ -6,6 +6,11 @@ import IconArrowDown from "components/Icons/ArrowDown"; import { getOptionByValue } from "utils/misc"; import styles from "./styles.module.scss"; +/** + * Custom dropdown indicator. + * + * @returns {JSX.Element} + */ const DropdownIndicator = () => ( ); @@ -18,7 +23,7 @@ const selectComponents = { DropdownIndicator, IndicatorSeparator: () => null }; * @param {Object} props component properties * @param {string} [props.className] class name to be added to root element * @param {string} props.id control's id - * @param {string} [props.label] control' label + * @param {string} [props.label] control's label * @param {(v: string) => void} props.onChange on change handler * @param {Object} props.options options for dropdown * @param {'medium'|'small'} [props.size] control's size @@ -45,9 +50,12 @@ const SelectField = ({ setIsOpen(false); }, []); - const onOptionChange = useCallback((option) => { - onChange(option.value); - }, []); + const onOptionChange = useCallback( + (option) => { + onChange(option.value); + }, + [onChange] + ); return (
( -
+
+ ); Sidebar.propTypes = { @@ -28,7 +28,11 @@ Sidebar.propTypes = { }; const NAV_ITEMS = [ - { icon: WorkPeriods, label: "Working Periods", path: APP_BASE_PATH }, + { + icon: WorkPeriods, + label: "Working Periods", + path: `${APP_BASE_PATH}/work-periods`, + }, { icon: Freelancers, label: "Freelancers", diff --git a/src/components/Sidebar/styles.module.scss b/src/components/Sidebar/styles.module.scss index b9e07e7..fb525f7 100644 --- a/src/components/Sidebar/styles.module.scss +++ b/src/components/Sidebar/styles.module.scss @@ -3,14 +3,18 @@ .container { padding: 27px 24px; - width: $sidebar-width; background-color: #fff; @include desktop { + flex: 0 0 auto; padding: 27px 20px; + width: $sidebar-width; + min-height: calc(100vh - 60px); } } .menu { - margin-bottom: 45px; + margin-bottom: 20px; + border-bottom: 1px solid #e9e9e9; + padding-bottom: 20px; } diff --git a/src/components/SidebarSection/index.jsx b/src/components/SidebarSection/index.jsx index 64d9741..0cfaa0a 100644 --- a/src/components/SidebarSection/index.jsx +++ b/src/components/SidebarSection/index.jsx @@ -5,7 +5,7 @@ import SidebarSectionLabel from "components/SidebarSectionLabel"; import styles from "./styles.module.scss"; /** - * Displays sidebar's section. + * Displays sidebar's section with optional label. * * @param {Object} props component properties * @param {Object} props.children component children diff --git a/src/components/SidebarSectionLabel/index.jsx b/src/components/SidebarSectionLabel/index.jsx index a82915b..6bde427 100644 --- a/src/components/SidebarSectionLabel/index.jsx +++ b/src/components/SidebarSectionLabel/index.jsx @@ -4,7 +4,7 @@ import cn from "classnames"; import styles from "./styles.module.scss"; /** - * Displays sidebar label. + * Displays sidebar section's label. * * @param {Object} props component props * @param {string} [props.className] class name added to root element diff --git a/src/components/SortingControl/index.jsx b/src/components/SortingControl/index.jsx index f33ae91..812ac8b 100644 --- a/src/components/SortingControl/index.jsx +++ b/src/components/SortingControl/index.jsx @@ -48,6 +48,7 @@ const SortingControl = ({ className, onChange, sortBy, value }) => { SortingControl.propTypes = { className: PT.string, onChange: PT.func.isRequired, + sortBy: PT.string.isRequired, value: PT.oneOf([SORT_ORDER.ASC, SORT_ORDER.DESC, null, undefined]) .isRequired, }; diff --git a/src/components/TextField/index.jsx b/src/components/TextField/index.jsx deleted file mode 100644 index 4786d2f..0000000 --- a/src/components/TextField/index.jsx +++ /dev/null @@ -1,18 +0,0 @@ -import React, { useCallback } from "react"; -import styles from "./styles.module.scss"; - -const TextField = ({ className, onChange, placeholder, value }) => { - return ( -
- { - onChange(event.target.value); - }} - value={value} - /> -
- ); -}; - -export default TextField; diff --git a/src/components/TextField/styles.module.scss b/src/components/TextField/styles.module.scss deleted file mode 100644 index 1423310..0000000 --- a/src/components/TextField/styles.module.scss +++ /dev/null @@ -1,3 +0,0 @@ -.textField { - position: relative; -} diff --git a/src/components/WeekPicker/index.jsx b/src/components/WeekPicker/index.jsx index bdbc98c..8bc860b 100644 --- a/src/components/WeekPicker/index.jsx +++ b/src/components/WeekPicker/index.jsx @@ -1,4 +1,4 @@ -import React, { forwardRef, useCallback, useRef, useState } from "react"; +import React, { forwardRef, useCallback, useRef } from "react"; import PT from "prop-types"; import cn from "classnames"; import DatePicker from "react-datepicker"; @@ -9,14 +9,16 @@ import "react-datepicker/dist/react-datepicker.css"; import styles from "./styles.module.scss"; /** + * Displays a control which allows to select a specific date using dropdown calendar + * or "next" and "previous" buttons. This control is used for week selection. * * @param {Object} props - * @param {string} [props.className] - * @param {Object} props.startDate - * @param {Object} props.endDate - * @param {() => void} props.onWeekSelect - * @param {() => void} props.onNextWeekSelect - * @param {() => void} props.onPreviousWeekSelect + * @param {string} [props.className] class name to be added to root element + * @param {Object} props.startDate momentjs object describing selection's start date + * @param {Object} props.endDate momentjs object describing selection's end date + * @param {() => void} props.onWeekSelect function called on date select + * @param {() => void} props.onNextWeekSelect function called on next week button click + * @param {() => void} props.onPreviousWeekSelect function called on previous week button click * @returns {JSX.Element} */ const WeekPicker = ({ @@ -27,41 +29,46 @@ const WeekPicker = ({ onNextWeekSelect, onPreviousWeekSelect, }) => { - const onDateChange = useCallback((date) => { - onWeekSelect(date); - }, []); + const isOpenRef = useRef(false); + const wasInputClickedRef = useRef(false); - const onBtnPrevClick = useCallback( - (event) => { - event.stopPropagation(); - event.preventDefault(); - onPreviousWeekSelect(); - }, - [onPreviousWeekSelect] - ); + const onCalendarOpen = useCallback(() => { + isOpenRef.current = true; + wasInputClickedRef.current = false; + }, []); - const onBtnNextClick = useCallback( - (event) => { - event.stopPropagation(); - event.preventDefault(); - onNextWeekSelect(); - }, - [onNextWeekSelect] - ); + const onCalendarClose = useCallback(() => { + isOpenRef.current = false; + }, []); - const CustomInput = forwardRef(({ value, onClick }, ref) => ( + // @ts-ignore + const CustomInput = forwardRef(({ onClick }, ref) => (
{ + if (isOpenRef.current) { + wasInputClickedRef.current = true; + } + }} + onClick={() => { + if (!isOpenRef.current && !wasInputClickedRef.current) { + onClick(); + } + wasInputClickedRef.current = false; + }} tabIndex={0} role="button" > @@ -69,23 +76,34 @@ const WeekPicker = ({ {formatDateRange(startDate, endDate)}
)); + CustomInput.displayName = "CustomInput"; + CustomInput.propTypes = { + // @ts-ignore + onClick: PT.func, + }; return (
{ - const appEnv = - process.env.APPENV === "production" ? "production" : "development"; - - // eslint-disable-next-line no-console - console.log(`APPENV: "${appEnv}"`); - - return require(`./${appEnv}`); -})(); diff --git a/src/config/production.js b/src/config/production.js deleted file mode 100644 index 7d0f7c4..0000000 --- a/src/config/production.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - API: { - V5: "https://api.topcoder.com/v5", - }, - PLATFORM_WEBSITE_URL: "https://platform.topcoder.com", -}; diff --git a/src/constants/index.js b/src/constants/index.js index 1fb84fd..12e064e 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -1,3 +1,7 @@ +import { PLATFORM_WEBSITE_URL } from "../../config"; + +export { PLATFORM_WEBSITE_URL }; + export const APP_BASE_PATH = "/taas-admin"; export const TAAS_BASE_PATH = "/taas/teams"; diff --git a/src/constants/workPeriods.js b/src/constants/workPeriods.js index d80a3c4..cd8baf8 100644 --- a/src/constants/workPeriods.js +++ b/src/constants/workPeriods.js @@ -1,5 +1,5 @@ // @ts-ignore -import { API } from "../config"; +import { API } from "../../config"; import * as API_PAYMENT_STATUS from "./workPeriods/apiPaymentStatus"; import * as API_SORT_BY from "./workPeriods/apiSortBy"; import * as SORT_BY from "./workPeriods/sortBy"; @@ -19,13 +19,12 @@ export const REQUIRED_FIELDS = [ "projectId", "startDate", "endDate", - "customerRate", + "memberRate", "workPeriods.id", "workPeriods.projectId", "workPeriods.userHandle", "workPeriods.startDate", "workPeriods.endDate", - "workPeriods.customerRate", "workPeriods.paymentStatus", "workPeriods.daysWorked", ]; @@ -59,6 +58,7 @@ export const PAYMENT_STATUS_LABELS = { [PAYMENT_STATUS.PAID]: "Paid", [PAYMENT_STATUS.PENDING]: "Pending", [PAYMENT_STATUS.IN_PROGRESS]: "In Progress", + [PAYMENT_STATUS.UNDEFINED]: "Undefined", }; export const PAYMENT_STATUS_MAP = { @@ -71,7 +71,7 @@ export const PAYMENT_STATUS_MAP = { export const API_PAYMENT_STATUS_MAP = (function () { const obj = {}; for (let key in PAYMENT_STATUS_MAP) { - if (PAYMENT_STATUS_MAP.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(PAYMENT_STATUS_MAP, key)) { obj[PAYMENT_STATUS_MAP[key]] = key; } } diff --git a/src/constants/workPeriods/apiSortBy.js b/src/constants/workPeriods/apiSortBy.js index c0591f5..c1bfc14 100644 --- a/src/constants/workPeriods/apiSortBy.js +++ b/src/constants/workPeriods/apiSortBy.js @@ -1,6 +1,6 @@ export const USER_HANDLE = "workPeriods.userHandle"; export const START_DATE = "startDate"; export const END_DATE = "endDate"; -export const WEEKLY_RATE = "workPeriods.customerRate"; +export const WEEKLY_RATE = "memberRate"; export const PAYMENT_STATUS = "workPeriods.paymentStatus"; export const WORKING_DAYS = "workPeriods.daysWorked"; diff --git a/src/hoc/withAuthentication/index.js b/src/hoc/withAuthentication/index.js index efe9063..2508017 100644 --- a/src/hoc/withAuthentication/index.js +++ b/src/hoc/withAuthentication/index.js @@ -20,7 +20,7 @@ import LoadingIndicator from "components/LoadingIndicator"; import { authUserSuccess, authUserError } from "./actions"; export default function withAuthentication(Component) { - return (props) => { + return function AuthenticatedComponent(props) { const dispatch = useDispatch(); const { isLoggedIn, authError } = useSelector((state) => state.authUser); /* @@ -33,7 +33,7 @@ export default function withAuthentication(Component) { if (!isLoggedIn) { getAuthUserTokens() .then(({ tokenV3 }) => { - if (!!tokenV3) { + if (tokenV3) { const tokenData = decodeToken(tokenV3); dispatch( authUserSuccess( @@ -50,7 +50,7 @@ export default function withAuthentication(Component) { return () => { isUnmount = true; }; - }, [isLoggedIn]); + }, [isLoggedIn, dispatch]); return ( <> diff --git a/src/root.component.jsx b/src/root.component.jsx index aaaa7c8..6be922c 100644 --- a/src/root.component.jsx +++ b/src/root.component.jsx @@ -1,7 +1,7 @@ -import React, { useEffect } from "react"; +import React, { useLayoutEffect } from "react"; import { Provider } from "react-redux"; -import { Router } from "@reach/router"; -import ReduxToastr from "react-redux-toastr"; +import { Router, Redirect } from "@reach/router"; +// import ReduxToastr from "react-redux-toastr"; import store from "store"; import { disableSidebarForRoute } from "@topcoder/micro-frontends-navbar-app"; import WorkPeriods from "routes/WorkPeriods"; @@ -10,14 +10,19 @@ import { APP_BASE_PATH } from "./constants"; import "styles/global.scss"; export default function Root() { - useEffect(() => { + useLayoutEffect(() => { disableSidebarForRoute(`${APP_BASE_PATH}/*`); }, []); return ( - + + {/* ( diff --git a/src/routes/WorkPeriods/components/PaymentStatus/index.jsx b/src/routes/WorkPeriods/components/PaymentStatus/index.jsx index 47cb067..0bb1ad3 100644 --- a/src/routes/WorkPeriods/components/PaymentStatus/index.jsx +++ b/src/routes/WorkPeriods/components/PaymentStatus/index.jsx @@ -5,10 +5,11 @@ import { formatPaymentStatus } from "utils/formatters"; import styles from "./styles.module.scss"; /** + * Displays payment status for working period. * * @param {Object} props component properties - * @param {string} [props.className] - * @param {string} props.status + * @param {string} [props.className] class name to be added to root element + * @param {string} props.status one of PAYMENT_STATUS enum constants * @returns {JSX.Element} */ const PaymentStatus = ({ className, status }) => ( diff --git a/src/routes/WorkPeriods/components/PaymentStatus/styles.module.scss b/src/routes/WorkPeriods/components/PaymentStatus/styles.module.scss index 58b449a..139f939 100644 --- a/src/routes/WorkPeriods/components/PaymentStatus/styles.module.scss +++ b/src/routes/WorkPeriods/components/PaymentStatus/styles.module.scss @@ -1,7 +1,13 @@ +@import "styles/mixins"; + .container { display: inline-block; padding: 3px 8px 1px; + @include roboto-medium; + font-size: 12px; line-height: 16px; + letter-spacing: 0.5px; + white-space: nowrap; border-radius: 5px; background: #aaa; color: #fff; @@ -24,5 +30,5 @@ } .undefined { - background: #000; + background: #2a2a2a; } diff --git a/src/routes/WorkPeriods/components/PeriodCount/index.jsx b/src/routes/WorkPeriods/components/PeriodCount/index.jsx index 01f4f19..8e6dc95 100644 --- a/src/routes/WorkPeriods/components/PeriodCount/index.jsx +++ b/src/routes/WorkPeriods/components/PeriodCount/index.jsx @@ -6,7 +6,7 @@ import { getWorkPeriodsTotalCount } from "store/selectors/workPeriods"; import styles from "./styles.module.scss"; /** - * Displays the total number of working periods. + * Displays the total number of working periods for current filters. * * @param {Object} props component properties * @param {string} [props.className] class name to be added to root element diff --git a/src/routes/WorkPeriods/components/Filters/index.jsx b/src/routes/WorkPeriods/components/PeriodFilters/index.jsx similarity index 57% rename from src/routes/WorkPeriods/components/Filters/index.jsx rename to src/routes/WorkPeriods/components/PeriodFilters/index.jsx index 17dcacf..fff1eb6 100644 --- a/src/routes/WorkPeriods/components/Filters/index.jsx +++ b/src/routes/WorkPeriods/components/PeriodFilters/index.jsx @@ -3,14 +3,14 @@ import { useDispatch, useSelector } from "react-redux"; import debounce from "lodash/debounce"; import PT from "prop-types"; import cn from "classnames"; -import SidebarSection from "components/SidebarSection"; +// import SidebarSection from "components/SidebarSection"; import Button from "components/Button"; -import CheckboxList from "components/CheckboxList"; -import { PAYMENT_STATUS } from "constants/workPeriods"; +// import CheckboxList from "components/CheckboxList"; +// import { PAYMENT_STATUS } from "constants/workPeriods"; import { getWorkPeriodsFilters } from "store/selectors/workPeriods"; import { resetWorkPeriodsFilters, - setWorkPeriodsPaymentStatuses, + // setWorkPeriodsPaymentStatuses, setWorkPeriodsUserHandle, } from "store/actions/workPeriods"; import { loadWorkPeriodsPage as loadWorkingPeriodsPage } from "store/thunks/workPeriods"; @@ -19,45 +19,52 @@ import styles from "./styles.module.scss"; import SearchField from "components/SearchField"; /** - * Displays challenges' and gigs' menu and challenge filters. + * Displays working periods' filters like user handle search control or + * payment status checkboxes. * * @param {Object} props component properties * @param {string} [props.className] optional class name added to root element * @returns {JSX.Element} */ -const Filters = ({ className }) => { +const PeriodFilters = ({ className }) => { const dispatch = useDispatch(); const filters = useSelector(getWorkPeriodsFilters); - const { paymentStatuses, userHandle } = filters; + const { /*paymentStatuses,*/ userHandle } = filters; - const onUserHandleChange = useCallback((value) => { - dispatch(setWorkPeriodsUserHandle(value)); - }, []); + const onUserHandleChange = useCallback( + (value) => { + dispatch(setWorkPeriodsUserHandle(value)); + }, + [dispatch] + ); - const onPaymentStatusesChange = useCallback((statuses) => { - dispatch(setWorkPeriodsPaymentStatuses(statuses)); - }, []); + // const onPaymentStatusesChange = useCallback( + // (statuses) => { + // dispatch(setWorkPeriodsPaymentStatuses(statuses)); + // }, + // [dispatch] + // ); const onClearFilter = useCallback(() => { dispatch(resetWorkPeriodsFilters()); - }, []); + }, [dispatch]); const loadWorkingPeriodsFirstPage = useCallback( debounce( () => { dispatch(loadWorkingPeriodsPage(1)); }, - 200, + 300, { leading: false } ), - [] + [dispatch] ); - // Load challenges' first page when any filter option changes. + // Load working periods' first page when any filter option changes. useUpdateEffect(loadWorkingPeriodsFirstPage, [filters]); return ( -
+
{ value={userHandle} />
- + {/* - + */}
-
@@ -89,14 +91,14 @@ const Filters = ({ className }) => { ); }; -Filters.propTypes = { +PeriodFilters.propTypes = { className: PT.string, }; -const PAYMENT_STATUS_OPTIONS = [ - { value: PAYMENT_STATUS.PENDING, label: "Pending" }, - { value: PAYMENT_STATUS.PAID, label: "Paid" }, - { value: PAYMENT_STATUS.IN_PROGRESS, label: "In Progress" }, -]; +// const PAYMENT_STATUS_OPTIONS = [ +// { value: PAYMENT_STATUS.PENDING, label: "Pending" }, +// { value: PAYMENT_STATUS.PAID, label: "Paid" }, +// { value: PAYMENT_STATUS.IN_PROGRESS, label: "In Progress" }, +// ]; -export default Filters; +export default PeriodFilters; diff --git a/src/routes/WorkPeriods/components/Filters/styles.module.scss b/src/routes/WorkPeriods/components/PeriodFilters/styles.module.scss similarity index 90% rename from src/routes/WorkPeriods/components/Filters/styles.module.scss rename to src/routes/WorkPeriods/components/PeriodFilters/styles.module.scss index eb51f82..c68620a 100644 --- a/src/routes/WorkPeriods/components/Filters/styles.module.scss +++ b/src/routes/WorkPeriods/components/PeriodFilters/styles.module.scss @@ -1,7 +1,7 @@ @import "styles/mixins"; .handleSection { - margin: 47px 0 26px; + margin: 0 0 26px; } .buttons { diff --git a/src/routes/WorkPeriods/components/PeriodItem/index.jsx b/src/routes/WorkPeriods/components/PeriodItem/index.jsx index cb1209d..6862e87 100644 --- a/src/routes/WorkPeriods/components/PeriodItem/index.jsx +++ b/src/routes/WorkPeriods/components/PeriodItem/index.jsx @@ -1,21 +1,35 @@ import React, { memo, useCallback } from "react"; import PT from "prop-types"; -import cn from "classnames"; import Checkbox from "components/Checkbox"; import IntegerField from "components/IntegerField"; import PaymentStatus from "../PaymentStatus"; import { formatUserHandleLink } from "utils/formatters"; +import _ from "lodash"; import styles from "./styles.module.scss"; +/** + * Displays the working period data row to be used in PeriodList component. + * + * @param {Object} props component properties + * @param {boolean} props.isSelected whether the item is selected + * @param {Object} props.item object describing a working period + * @param {(v: string) => void} props.onToggle function called when working period checkbox is clicked + * @param {(v: { periodId: string, workingDays: number }) => void} props.onWorkingDaysChange + * function called when the number of working days is changed + * @returns {JSX.Element} + */ const PeriodItem = ({ isSelected, item, onToggle, onWorkingDaysChange }) => { - const onToggleItem = useCallback((event) => { - onToggle(event.target.value); - }, []); + const onToggleItem = useCallback( + (event) => { + onToggle(event.target.value); + }, + [onToggle] + ); const onDaysChange = useCallback( (workingDays) => { onWorkingDaysChange({ periodId: item.id, workingDays }); }, - [item] + [item, onWorkingDaysChange] ); return ( @@ -30,15 +44,19 @@ const PeriodItem = ({ isSelected, item, onToggle, onWorkingDaysChange }) => { - + {item.userHandle} - {item.projectId} - {item.startDate} - {item.endDate} - {currencyFormatter.format(item.weeklyRate)} + {item.projectId} + {item.startDate} + {item.endDate} + {_.isNumber(item.weeklyRate) ? currencyFormatter.format(item.weeklyRate) : '-'} @@ -61,6 +79,8 @@ PeriodItem.propTypes = { isSelected: PT.bool.isRequired, item: PT.shape({ id: PT.oneOfType([PT.number, PT.string]).isRequired, + rbId: PT.string.isRequired, + projectId: PT.oneOfType([PT.number, PT.string]).isRequired, userHandle: PT.string.isRequired, teamName: PT.oneOfType([PT.number, PT.string]).isRequired, startDate: PT.string.isRequired, @@ -69,6 +89,8 @@ PeriodItem.propTypes = { paymentStatus: PT.string.isRequired, workingDays: PT.number.isRequired, }), + onToggle: PT.func.isRequired, + onWorkingDaysChange: PT.func.isRequired, }; const currencyFormatter = new Intl.NumberFormat("en-US", { diff --git a/src/routes/WorkPeriods/components/PeriodItem/styles.module.scss b/src/routes/WorkPeriods/components/PeriodItem/styles.module.scss index b63f9c3..9cf123f 100644 --- a/src/routes/WorkPeriods/components/PeriodItem/styles.module.scss +++ b/src/routes/WorkPeriods/components/PeriodItem/styles.module.scss @@ -7,7 +7,7 @@ background: #fff; } - &:nth-child(even) { + &:nth-child(odd) { td { background: #f9f9f9; } @@ -15,13 +15,14 @@ } td.toggle { - padding: 12px 20px 12px 30px; + padding: 12px 20px 12px 15px; line-height: 15px; } .userHandle { a { @include roboto-bold; + color: #0d61bf; } span { display: block; @@ -32,8 +33,14 @@ td.toggle { } } -.teamName { +td.teamName { @include roboto-medium; + white-space: nowrap; +} + +td.startDate, +td.endDate { + white-space: nowrap; } td.workingDays { diff --git a/src/routes/WorkPeriods/components/PeriodList/index.jsx b/src/routes/WorkPeriods/components/PeriodList/index.jsx index e80b45d..5cb6f88 100644 --- a/src/routes/WorkPeriods/components/PeriodList/index.jsx +++ b/src/routes/WorkPeriods/components/PeriodList/index.jsx @@ -15,10 +15,10 @@ import { import styles from "./styles.module.scss"; /** - * Displays the main content of the challenges' page. + * Displays the list of the working periods with column headers. * * @param {Object} props component properties - * @param {string} [props.className] + * @param {string} [props.className] class name to be added to root element * @returns {JSX.Element} */ const PeriodList = ({ className }) => { @@ -26,31 +26,42 @@ const PeriodList = ({ className }) => { const periodsSelected = useSelector(getWorkPeriodsSelected); const dispatch = useDispatch(); - const onTogglePeriod = useCallback((periodId) => { - dispatch(toggleWorkPeriod(periodId)); - }, []); + const onTogglePeriod = useCallback( + (periodId) => { + dispatch(toggleWorkPeriod(periodId)); + }, + [dispatch] + ); - const onWorkingDaysChange = useCallback((payload) => { - dispatch(setWorkPeriodWorkingDays(payload)); - }, []); + const onWorkingDaysChange = useCallback( + (payload) => { + dispatch(setWorkPeriodWorkingDays(payload)); + }, + [dispatch] + ); return ( - - - - - - {periods.map((period) => ( - - ))} - -
+
+ + + + + + + + + {periods.map((period) => ( + + ))} + +
+
); }; diff --git a/src/routes/WorkPeriods/components/PeriodList/styles.module.scss b/src/routes/WorkPeriods/components/PeriodList/styles.module.scss index 78ef9e8..9bb7222 100644 --- a/src/routes/WorkPeriods/components/PeriodList/styles.module.scss +++ b/src/routes/WorkPeriods/components/PeriodList/styles.module.scss @@ -1,5 +1,24 @@ @import "styles/mixins"; .container { + position: relative; + padding: 0 20px 0 15px; +} + +.table { width: 100%; } + +.listTopMargin { + padding: 0 0 7px; + background: #fff !important; + + &::before { + content: ""; + display: block; + height: 1px; + margin-left: -15px; + margin-right: -20px; + background: #e9e9e9; + } +} diff --git a/src/routes/WorkPeriods/components/PeriodListHead/index.jsx b/src/routes/WorkPeriods/components/PeriodListHead/index.jsx index c9b2f8f..175618b 100644 --- a/src/routes/WorkPeriods/components/PeriodListHead/index.jsx +++ b/src/routes/WorkPeriods/components/PeriodListHead/index.jsx @@ -4,7 +4,6 @@ import cn from "classnames"; import Checkbox from "components/Checkbox"; import SortingControl from "components/SortingControl"; import { SORT_BY } from "constants/workPeriods"; -import { useUpdateEffect } from "utils/hooks"; import { getWorkPeriodsIsSelectedVisible, getWorkPeriodsSorting, @@ -13,27 +12,35 @@ import { setWorkPeriodsSorting, toggleWorkingPeriodsVisible, } from "store/actions/workPeriods"; -import { loadWorkPeriodsPage } from "store/thunks/workPeriods"; import styles from "./styles.module.scss"; +/** + * Displays working period list column heads with sorting controls to be used + * in PeriodList component. + * + * @returns {JSX.Element} + */ const PeriodListHead = () => { const sorting = useSelector(getWorkPeriodsSorting); const isSelectedVisible = useSelector(getWorkPeriodsIsSelectedVisible); const dispatch = useDispatch(); const { criteria, order } = sorting; - const onSortingChange = useCallback((sorting) => { - dispatch(setWorkPeriodsSorting(sorting)); - }, []); + const onSortingChange = useCallback( + (sorting) => { + dispatch(setWorkPeriodsSorting(sorting)); + }, + [dispatch] + ); const onToggleVisible = useCallback(() => { dispatch(toggleWorkingPeriodsVisible()); - }, []); + }, [dispatch]); return ( -
+
{ const [startDate, endDate] = useSelector(getWorkPeriodsDateRange); const dispatch = useDispatch(); - const onWeekSelect = useCallback((date) => { - dispatch(setWorkPeriodsDateRange(moment(date))); - }, []); + const onWeekSelect = useCallback( + (date) => { + dispatch(setWorkPeriodsDateRange(moment(date))); + }, + [dispatch] + ); const onNextWeekSelect = useCallback(() => { dispatch(setWorkPeriodsDateRange(startDate.clone().add(1, "week"))); - }, [startDate]); + }, [startDate, dispatch]); const onPreviousWeekSelect = useCallback(() => { dispatch(setWorkPeriodsDateRange(startDate.clone().add(-1, "week"))); - }, [startDate]); + }, [startDate, dispatch]); return ( { ); }; +PeriodWeekPicker.propTypes = { + className: PT.string, +}; + export default PeriodWeekPicker; diff --git a/src/routes/WorkPeriods/components/Periods/index.jsx b/src/routes/WorkPeriods/components/Periods/index.jsx index d35225e..3f971da 100644 --- a/src/routes/WorkPeriods/components/Periods/index.jsx +++ b/src/routes/WorkPeriods/components/Periods/index.jsx @@ -5,23 +5,34 @@ import PeriodList from "../PeriodList"; import { getWorkPeriodsError, getWorkPeriodsIsLoading, + getWorkPeriodsPagination, getWorkPeriodsSorting, - getWorkPeriodsTotalCount, } from "store/selectors/workPeriods"; import { loadWorkPeriodsPage } from "store/thunks/workPeriods"; +import { useUpdateEffect } from "utils/hooks"; +/** + * Displays working periods' list or a "Loading..." message or an error message. + * + * @returns {JSX.Element} + */ const Periods = () => { + const pagination = useSelector(getWorkPeriodsPagination); const sorting = useSelector(getWorkPeriodsSorting); - const count = useSelector(getWorkPeriodsTotalCount); const error = useSelector(getWorkPeriodsError); const isLoading = useSelector(getWorkPeriodsIsLoading); const dispatch = useDispatch(); // Load working periods' first page once when page loads and then - // only if sorting changes. + // only if page size or sorting changes. useEffect(() => { dispatch(loadWorkPeriodsPage(1)); - }, [sorting]); + }, [dispatch, pagination.pageSize, sorting]); + + // Load working periods' new page if page number changes. + useUpdateEffect(() => { + dispatch(loadWorkPeriodsPage()); + }, [dispatch, pagination.pageNumber]); return ( <> @@ -30,7 +41,7 @@ const Periods = () => { {!isLoading && error && ( {error} )} - {!isLoading && !error && !count && ( + {!isLoading && !error && !pagination.totalCount && ( No resource bookings found. )} diff --git a/src/routes/WorkPeriods/components/PeriodsPagination/index.jsx b/src/routes/WorkPeriods/components/PeriodsPagination/index.jsx index 40e7175..cec8594 100644 --- a/src/routes/WorkPeriods/components/PeriodsPagination/index.jsx +++ b/src/routes/WorkPeriods/components/PeriodsPagination/index.jsx @@ -1,5 +1,5 @@ -/* eslint-disable react-hooks/exhaustive-deps */ import React, { useCallback } from "react"; +import PT from "prop-types"; import cn from "classnames"; import { useDispatch, useSelector } from "react-redux"; import Pagination from "components/Pagination"; @@ -8,12 +8,10 @@ import { setWorkPeriodsPageNumber, setWorkPeriodsPageSize, } from "store/actions/workPeriods"; -import { loadWorkPeriodsPage } from "store/thunks/workPeriods"; -import { useUpdateEffect } from "utils/hooks"; import styles from "./styles.module.scss"; /** - * Displays challenges' pagination and a menu to choose page size. + * Displays working periods' pagination and a menu to choose page size. * * @param {Object} props component properties * @param {string} [props.className] class name added to root element @@ -24,31 +22,24 @@ const PeriodsPagination = ({ className, id }) => { const pagination = useSelector(getWorkPeriodsPagination); const dispatch = useDispatch(); - const onPageNumberClick = useCallback((pageNumber) => { - dispatch(setWorkPeriodsPageNumber(+pageNumber)); - }, []); - - const onPageSizeChange = useCallback((pageSize) => { - dispatch(setWorkPeriodsPageSize(+pageSize)); - }, []); - - const loadWorkPeriodsFirstPage = useCallback(() => { - dispatch(loadWorkPeriodsPage(1)); - }, []); - - const loadWorkPeriodsNewPage = useCallback(() => { - dispatch(loadWorkPeriodsPage()); - }, []); - - // Load challenges' first page if page size changes. - useUpdateEffect(loadWorkPeriodsFirstPage, [pagination.pageSize]); + const onPageNumberClick = useCallback( + (pageNumber) => { + dispatch(setWorkPeriodsPageNumber(+pageNumber)); + }, + [dispatch] + ); - // Load challenges' new page if page number changes. - useUpdateEffect(loadWorkPeriodsNewPage, [pagination.pageNumber]); + const onPageSizeChange = useCallback( + (pageSize) => { + dispatch(setWorkPeriodsPageSize(+pageSize)); + }, + [dispatch] + ); return ( { ); }; +PeriodsPagination.propTypes = { + className: PT.string, + id: PT.string.isRequired, +}; + export default PeriodsPagination; const PAGE_SIZE_OPTIONS = [ diff --git a/src/routes/WorkPeriods/index.jsx b/src/routes/WorkPeriods/index.jsx index 4458380..3bb972f 100644 --- a/src/routes/WorkPeriods/index.jsx +++ b/src/routes/WorkPeriods/index.jsx @@ -7,22 +7,27 @@ import ContentBlock from "components/ContentBlock"; import Button from "components/Button"; import Page from "components/Page"; import PageTitle from "components/PageTitle"; -import Filters from "./components/Filters"; +import PeriodFilters from "./components/PeriodFilters"; import Periods from "./components/Periods"; import PeriodCount from "./components/PeriodCount"; import PeriodsPagination from "./components/PeriodsPagination"; import styles from "./styles.module.scss"; import PeriodWeekPicker from "./components/PeriodWeekPicker"; +/** + * Displays route component for Working Days' route. + * + * @returns {JSX.Element} + */ const WorkPeriods = () => ( - + - @@ -31,7 +36,7 @@ const WorkPeriods = () => (
diff --git a/src/routes/WorkPeriods/styles.module.scss b/src/routes/WorkPeriods/styles.module.scss index 33a3210..bedfd27 100644 --- a/src/routes/WorkPeriods/styles.module.scss +++ b/src/routes/WorkPeriods/styles.module.scss @@ -22,9 +22,12 @@ } .periodCount { - } .periodWeekPicker { margin-left: 40px; } + +.periodsPagination { + margin-left: 40px; +} diff --git a/src/store/reducers/workPeriods.js b/src/store/reducers/workPeriods.js index 019f079..25b1089 100644 --- a/src/store/reducers/workPeriods.js +++ b/src/store/reducers/workPeriods.js @@ -165,7 +165,10 @@ const actionHandlers = { for (let i = 0, len = oldPeriods.length; i < len; i++) { let period = oldPeriods[i]; if (period.id === periodId) { - period = { ...period, workingDays: Math.max(workingDays, 0) }; + period = { + ...period, + workingDays: Math.min(Math.max(workingDays, 0), 5), + }; } periods.push(period); } @@ -175,16 +178,22 @@ const actionHandlers = { }; }, [ACTION_TYPE.WP_TOGGLE_PERIOD]: (state, periodId) => { + let isSelectedPeriodsAll = state.isSelectedPeriodsAll; + let isSelectedPeriodsVisible = state.isSelectedPeriodsVisible; const periodsSelected = { ...state.periodsSelected }; const isSelected = !periodsSelected[periodId]; if (isSelected) { periodsSelected[periodId] = true; } else { + isSelectedPeriodsAll = false; + isSelectedPeriodsVisible = false; delete periodsSelected[periodId]; } return { ...state, periodsSelected, + isSelectedPeriodsAll, + isSelectedPeriodsVisible, }; }, [ACTION_TYPE.WP_TOGGLE_PERIODS_ALL]: (state) => { diff --git a/src/store/thunks/workPeriods.js b/src/store/thunks/workPeriods.js index fe80837..cd2b1a7 100644 --- a/src/store/thunks/workPeriods.js +++ b/src/store/thunks/workPeriods.js @@ -1,5 +1,4 @@ import axios from "axios"; -import moment from "moment"; import * as actions from "store/actions/workPeriods"; import * as selectors from "store/selectors/workPeriods"; import * as services from "services/workPeriods"; @@ -16,13 +15,12 @@ import { replaceItems, } from "utils/misc"; import { normalizePeriodItems } from "utils/workPeriods"; -import { USER_HANDLE } from "constants/workPeriods/apiSortBy"; /** - * Thunk that loads the specified challenges' page. If page number is not + * Thunk that loads the specified working periods' page. If page number is not * provided the current page number from current state is used. All relevant - * challenge parameters are loaded from the current state to construct a request - * query. + * working period filters are loaded from the current state to construct + * a request query. * * @param {number} [pageNumber] page number to load * @returns {function} @@ -44,7 +42,7 @@ export const loadWorkPeriodsPage = const sortOrder = sorting.order; const sortBy = SORT_BY_MAP[sorting.criteria] || API_SORT_BY.USER_HANDLE; - const [startDate, endDate] = filters.dateRange; + const [startDate] = filters.dateRange; const paymentStatuses = replaceItems( Object.keys(filters.paymentStatuses), PAYMENT_STATUS_MAP @@ -68,19 +66,19 @@ export const loadWorkPeriodsPage = // paymentStatuses, }); dispatch(actions.loadWorkPeriodsPagePending(cancelSource, pageNumber)); - let totalCount, periods, pageCount, pageSize; + let totalCount, periods, pageCount; try { const response = await promise; - ({ totalCount, pageNumber, pageCount, pageSize } = + ({ totalCount, pageNumber, pageCount } = extractResponsePagination(response)); const data = extractResponseData(response); periods = normalizePeriodItems(data); } catch (error) { + // If request was cancelled by the next call to loadWorkPeriodsPage + // there's nothing more to do. if (!axios.isCancel(error)) { dispatch(actions.loadWorkPeriodsPageError(error.toString())); } - // If request was cancelled by the next call to loadWorkPeriodsPage - // there's nothing more to do. return; } dispatch( diff --git a/src/styles/global.scss b/src/styles/global.scss index 441dd50..9a0cbff 100644 --- a/src/styles/global.scss +++ b/src/styles/global.scss @@ -1,4 +1 @@ -// @import 'fonts'; -@import "normalize"; -@import "layout"; @import "toastr"; diff --git a/src/styles/layout.scss b/src/styles/layout.scss deleted file mode 100644 index 66db9c3..0000000 --- a/src/styles/layout.scss +++ /dev/null @@ -1,31 +0,0 @@ -html, -body { - height: 100%; -} - -body { - display: flex; - flex-direction: column; - line-height: (22/14); - overflow-x: auto; - overflow-y: scroll; - - > nav { - flex: 0 0 auto; - } -} - -#single-spa-main { - flex: 1 0 auto; - display: flex; - - > div { - flex: 1 0 auto; - display: flex; - - > div:first-child { - flex: 1 0 auto; - display: flex; - } - } -} diff --git a/src/styles/normalize.scss b/src/styles/normalize.scss deleted file mode 100644 index e47847f..0000000 --- a/src/styles/normalize.scss +++ /dev/null @@ -1,23 +0,0 @@ -@import "mixins"; -@import "variables"; - -html { - background-color: $page-bg-color !important; -} - -#single-spa-main *, -#single-spa-main *::before, -#single-spa-main *::after { - box-sizing: border-box; -} - -#single-spa-main { - @include roboto-regular; - font-size: 14px; - line-height: (22/14); - color: $text-color; -} - -strong { - font-weight: 700; -} diff --git a/src/styles/variables/_colors.scss b/src/styles/variables/_colors.scss index 19030eb..8a9b503 100644 --- a/src/styles/variables/_colors.scss +++ b/src/styles/variables/_colors.scss @@ -1,8 +1,11 @@ -$primary-color: #0ab88a; -$primary-dark-color: #137d60; -$primary-dark-text-color: #229174; +$primary-color: #137d60; +$primary-text-color: #229174; +$primary-light-color: #0ab88a; +$primary-light-text-color: #0ab88a; // currently not used, cen be changed +$primary-dark-color: #137d60; // currently not used, cen be changed +$primary-dark-text-color: #137d60; // currently not used, cen be changed $text-color: #2a2a2a; $page-bg-color: #f4f5f6; $control-border-color: #aaa; -$checkbox-bg-color: $primary-color; +$checkbox-bg-color: $primary-light-color; diff --git a/src/topcoder-micro-frontends-taas-admin-app.js b/src/topcoder-micro-frontends-taas-admin-app.js index ae388ad..948ab39 100644 --- a/src/topcoder-micro-frontends-taas-admin-app.js +++ b/src/topcoder-micro-frontends-taas-admin-app.js @@ -8,7 +8,7 @@ const lifecycles = singleSpaReact({ React, ReactDOM, rootComponent: Root, - errorBoundary(err, info, props) { + errorBoundary(/*err, info, props*/) { // Customize the root error boundary for your microfrontend here. return null; }, diff --git a/src/utils/formatters.js b/src/utils/formatters.js index f5d3ecb..ecad6ab 100644 --- a/src/utils/formatters.js +++ b/src/utils/formatters.js @@ -1,22 +1,26 @@ // @ts-ignore -import { PLATFORM_WEBSITE_URL } from "../config"; -import { TAAS_BASE_PATH } from "../constants"; +import { PAYMENT_STATUS_LABELS } from "constants/workPeriods"; +import { PLATFORM_WEBSITE_URL, TAAS_BASE_PATH } from "../constants"; const rxWhitespace = /\s+/; /** * Formats payment status. * - * @param {string} status payment status + * @param {string} status payment status as defined by PAYMENT_STATUS enum constant * @returns {string} */ export function formatPaymentStatus(status) { - let words = status.split(rxWhitespace); - for (let i = 0, len = words.length; i < len; i++) { - let word = words[i]; - words[i] = word.slice(0, 1).toUpperCase() + word.slice(1).toLowerCase(); + let paymentStatus = PAYMENT_STATUS_LABELS[status]; + if (!paymentStatus) { + let words = status.split(rxWhitespace); + for (let i = 0, len = words.length; i < len; i++) { + let word = words[i]; + words[i] = word.slice(0, 1).toUpperCase() + word.slice(1).toLowerCase(); + } + paymentStatus = words.join(" "); } - return words.join(" "); + return paymentStatus; } /** diff --git a/src/utils/hooks.js b/src/utils/hooks.js index 8200488..82e138f 100644 --- a/src/utils/hooks.js +++ b/src/utils/hooks.js @@ -16,5 +16,5 @@ export const useUpdateEffect = (effect, deps) => { isMountedRef.current = true; } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [effect, ...deps]); + }, deps); }; diff --git a/src/utils/misc.js b/src/utils/misc.js index 0031391..f931c62 100644 --- a/src/utils/misc.js +++ b/src/utils/misc.js @@ -58,7 +58,7 @@ export function replaceItems(array, map) { export function updateOptionMap(oldOptions, newOptions) { oldOptions = { ...oldOptions }; for (let key in newOptions) { - if (newOptions.hasOwnProperty(key)) { + if (Object.prototype.hasOwnProperty.call(newOptions, key)) { let value = newOptions[key]; if (value) { oldOptions[key] = true; diff --git a/src/utils/workPeriods.js b/src/utils/workPeriods.js index fecca9a..2258b73 100644 --- a/src/utils/workPeriods.js +++ b/src/utils/workPeriods.js @@ -20,11 +20,11 @@ export function normalizePeriodItems(items) { ? moment(item.startDate).format(DATE_FORMAT_UI) : "", endDate: item.endDate ? moment(item.endDate).format(DATE_FORMAT_UI) : "", - weeklyRate: +workPeriod.customerRate || 0, + weeklyRate: item.memberRate, paymentStatus: paymentStatus ? API_PAYMENT_STATUS_MAP[paymentStatus] || paymentStatus.toUpperCase() : PAYMENT_STATUS.UNDEFINED, - workingDays: daysWorked === null ? 5 : +daysWorked || 5, + workingDays: daysWorked === null ? 5 : +daysWorked || 0, }); } return periods; diff --git a/webpack.config.js b/webpack.config.js index 64fb056..afbbf1b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -63,18 +63,21 @@ module.exports = (webpackConfigEnv) => { }, ], }, - // { - // test: /\.svg$/, - // exclude: /node_modules/, - // use: { - // loader: "babel-loader", - // }, - // }, + { + test: /\.svg$/, + exclude: /node_modules/, + use: { + loader: "babel-loader", + }, + }, { test: /\.(gif|jpg|png|svg)$/, include: /.*assets[/\\]images.+/, use: { - loader: "file-loader", + loader: "url-loader", + options: { + limit: 4096, + }, }, }, ],