From 7f26191887fca397d30a8987535667a87afea26d Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Thu, 1 Sep 2022 10:46:43 +0800 Subject: [PATCH 001/113] integrate cy dashboard --- .circleci/config.yml | 3 --- .gitignore | 2 ++ cypress.config.ts | 7 ++++--- cypress/e2e/home/home.spec.ts | 4 ++++ package.json | 3 ++- yarn.lock | 20 ++++++++++++++++++++ 6 files changed, 32 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index dd0d77bc6..42d07afff 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -155,9 +155,6 @@ jobs: - checkout - restore_cache: key: test-node-modules-{{ checksum "yarn.lock" }} - - run: - name: Config Git - command: git config --global url."https://git@".insteadOf git:// - run: name: Install Dependencies command: yarn install diff --git a/.gitignore b/.gitignore index 63c983693..00cac0197 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ # testing /coverage .nyc_output +/cypress/screenshots +/cypress/videos # production /build diff --git a/cypress.config.ts b/cypress.config.ts index e622ea360..64e42e01a 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,9 +1,10 @@ import { defineConfig } from 'cypress' export default defineConfig({ + projectId: "yfm67z", fixturesFolder: false, - video: false, - screenshotOnRunFailure: false, + video: true, + screenshotOnRunFailure: true, defaultCommandTimeout: 10000, e2e: { baseUrl: 'http://localhost:3000', @@ -15,5 +16,5 @@ export default defineConfig({ require('@cypress/code-coverage/task')(on, config) return config; }, - }, + } }) diff --git a/cypress/e2e/home/home.spec.ts b/cypress/e2e/home/home.spec.ts index 8d1f98c25..960cda452 100644 --- a/cypress/e2e/home/home.spec.ts +++ b/cypress/e2e/home/home.spec.ts @@ -5,4 +5,8 @@ describe('Landing Page', () => { it('loads landing page should be successfully', () => { cy.get('[data-id="root"]').should('be.visible') }) + + it('loads landing page should fail', () => { + cy.get('[data-id="root"]').should('not.be.visible') + }) }) diff --git a/package.json b/package.json index bf9628928..5f619c046 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "eslint:fix": "eslint 'src/**/*.{js,jsx}' --fix", "test": "react-scripts test --watchAll", "test:no-watch": "react-scripts test --watchAll=false --passWithNoTests", - "cy:run": "cypress run", + "cy:run": "dotenv -- cypress run --record --key f290b5f1-3586-45a6-ba5f-aa33b68e4868", "cy:ci": "start-server-and-test 'serve -s build -n -p 3000' http://localhost:3000 'cy:run'", "report:coverage": "nyc report --reporter=html", "report:coverage:text": "nyc report --reporter=text" @@ -104,6 +104,7 @@ "config": "^3.3.6", "cross-env": "^7.0.2", "cypress": "^10.6.0", + "dotenv-cli": "^6.0.0", "eslint": "^8.18.0", "eslint-config-prettier": "^6.7.0", "eslint-config-react-app": "^7.0.1", diff --git a/yarn.lock b/yarn.lock index 301fcda10..66b5c757f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6071,16 +6071,36 @@ dot-case@^3.0.4: no-case "^3.0.4" tslib "^2.0.3" +dotenv-cli@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/dotenv-cli/-/dotenv-cli-6.0.0.tgz#8a30cbc59d0a8aaa166b2fee0a9a55e23a1223ab" + integrity sha512-qXlCOi3UMDhCWFKe0yq5sg3X+pJAz+RQDiFN38AMSbUrnY3uZshSfDJUAge951OS7J9gwLZGfsBlWRSOYz/TRg== + dependencies: + cross-spawn "^7.0.3" + dotenv "^16.0.0" + dotenv-expand "^8.0.1" + minimist "^1.2.5" + dotenv-expand@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== +dotenv-expand@^8.0.1: + version "8.0.3" + resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-8.0.3.tgz#29016757455bcc748469c83a19b36aaf2b83dd6e" + integrity sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg== + dotenv@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== +dotenv@^16.0.0: + version "16.0.2" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.2.tgz#0b0f8652c016a3858ef795024508cddc4bffc5bf" + integrity sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA== + duplexer@^0.1.1, duplexer@^0.1.2, duplexer@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" From dc8b73bf124e0bc8dd2c93530818ec3bca1bdf91 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Thu, 1 Sep 2022 14:35:23 +0800 Subject: [PATCH 002/113] refine ci --- .circleci/config.yml | 34 ++++------------------------------ 1 file changed, 4 insertions(+), 30 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 42d07afff..f01198fe7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,6 +14,7 @@ install_build_dependency: &install_build_dependency apt update apt install jq -y apt install python3-pip -y + apt-get install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb pip3 install awscli --upgrade install_dependency: &install_dependency @@ -36,6 +37,7 @@ save_cache_settings: &save_cache_settings key: connect-node-modules-{{ checksum "yarn.lock" }} paths: - node_modules + - /root/.cache/Cypress running_yarn_tslint: &running_yarn_tslint name: Running Yarn tslint @@ -48,7 +50,9 @@ running_yarn_build: &running_yarn_build command: | source buildenvvar yarn install + yarn cypress install yarn build + yarn cy:ci workspace_persist: &workspace_persist root: . @@ -147,32 +151,6 @@ jobs: APPNAME: "platform-ui-mvp" steps: *deploy_steps - # Test job for the cases when we don not need deployment. - e2e-test: - docker: - - image: cypress/browsers:node16.14.2-slim-chrome100-ff99-edge - steps: - - checkout - - restore_cache: - key: test-node-modules-{{ checksum "yarn.lock" }} - - run: - name: Install Dependencies - command: yarn install - no_output_timeout: 20m - - run: - name: Install Cypress Binary - command: yarn cypress install - - run: - name: Build the application - command: yarn build - no_output_timeout: 20m - - save_cache: - key: test-node-modules-{{ checksum "yarn.lock" }} - paths: - - node_modules - - /root/.cache/Cypress - - run: yarn cy:ci - workflows: version: 2 build: @@ -198,9 +176,6 @@ workflows: ignore: - master - - e2e-test: - context : org-global - - build-prod: context : org-global filters: @@ -212,7 +187,6 @@ workflows: context : org-global requires: - build-dev - - e2e-test filters: branches: only: From f1fcdc86dd0454fd79dc063afc93eff544271330 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Thu, 1 Sep 2022 14:44:49 +0800 Subject: [PATCH 003/113] refine command --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f01198fe7..9ab068ee1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -14,7 +14,7 @@ install_build_dependency: &install_build_dependency apt update apt install jq -y apt install python3-pip -y - apt-get install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb + apt install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb -y pip3 install awscli --upgrade install_dependency: &install_dependency From 19249261588575dc982c5f8ee5cb20d9db19fcdb Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Fri, 2 Sep 2022 10:55:55 +0800 Subject: [PATCH 004/113] report integration --- .circleci/config.yml | 8 ++ .gitignore | 1 + cypress.config.ts | 1 - package.json | 4 +- yarn.lock | 313 +++++++++++++++++++++++++++++++++++++++---- 5 files changed, 296 insertions(+), 31 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 9ab068ee1..79394a923 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -83,6 +83,14 @@ build_steps: &build_steps - run: *install_deploysuite - run: *build_configuration_fetch - run: *running_yarn_build + - store_test_results: + path: test-results.xml + - store_artifacts: + path: test-results.xml + - store_artifacts: + path: cypress/videos + - store_artifacts: + path: cypress/screenshots - persist_to_workspace: *workspace_persist deploy_steps: &deploy_steps diff --git a/.gitignore b/.gitignore index 00cac0197..4ac1f11aa 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ .nyc_output /cypress/screenshots /cypress/videos +test-results.xml # production /build diff --git a/cypress.config.ts b/cypress.config.ts index 64e42e01a..82fbc1a8f 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,7 +1,6 @@ import { defineConfig } from 'cypress' export default defineConfig({ - projectId: "yfm67z", fixturesFolder: false, video: true, screenshotOnRunFailure: true, diff --git a/package.json b/package.json index 5f619c046..657094294 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "eslint:fix": "eslint 'src/**/*.{js,jsx}' --fix", "test": "react-scripts test --watchAll", "test:no-watch": "react-scripts test --watchAll=false --passWithNoTests", - "cy:run": "dotenv -- cypress run --record --key f290b5f1-3586-45a6-ba5f-aa33b68e4868", + "cy:run": "cypress run --reporter junit", "cy:ci": "start-server-and-test 'serve -s build -n -p 3000' http://localhost:3000 'cy:run'", "report:coverage": "nyc report --reporter=html", "report:coverage:text": "nyc report --reporter=text" @@ -94,6 +94,7 @@ "@types/segment-analytics": "^0.0.34", "@types/systemjs": "^6.1.0", "@types/uuid": "^8.3.4", + "@wdio/junit-reporter": "^7.24.0", "autoprefixer": "^9.8.6", "babel-eslint": "^11.0.0-beta.2", "babel-jest": "^24.9.0", @@ -104,7 +105,6 @@ "config": "^3.3.6", "cross-env": "^7.0.2", "cypress": "^10.6.0", - "dotenv-cli": "^6.0.0", "eslint": "^8.18.0", "eslint-config-prettier": "^6.7.0", "eslint-config-react-app": "^7.0.1", diff --git a/yarn.lock b/yarn.lock index 66b5c757f..32d30477e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2057,6 +2057,11 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.24.28.tgz#15aa0b416f82c268b1573ab653e4413c965fe794" integrity sha512-dgJd3HLOkLmz4Bw50eZx/zJwtBq65nms3N9VBYu5LTjJ883oBFkTyXRlCB/ZGGwqYpJJHA5zW2Ibhl5ngITfow== +"@sindresorhus/is@^4.0.0": + version "4.6.0" + resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f" + integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw== + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d" @@ -2196,6 +2201,13 @@ "@svgr/plugin-svgo" "^5.5.0" loader-utils "^2.0.0" +"@szmarczak/http-timer@^4.0.5": + version "4.0.6" + resolved "https://registry.yarnpkg.com/@szmarczak/http-timer/-/http-timer-4.0.6.tgz#b4a914bb62e7c272d4e5989fe4440f812ab1d807" + integrity sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w== + dependencies: + defer-to-connect "^2.0.0" + "@testing-library/dom@^8.0.0": version "8.17.1" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-8.17.1.tgz#2d7af4ff6dad8d837630fecd08835aee08320ad7" @@ -2311,6 +2323,16 @@ dependencies: "@types/node" "*" +"@types/cacheable-request@^6.0.1": + version "6.0.2" + resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.2.tgz#c324da0197de0a98a2312156536ae262429ff6b9" + integrity sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA== + dependencies: + "@types/http-cache-semantics" "*" + "@types/keyv" "*" + "@types/node" "*" + "@types/responselike" "*" + "@types/connect-history-api-fallback@^1.3.5": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz#d1f7a8a09d0ed5a57aee5ae9c18ab9b803205dae" @@ -2326,6 +2348,11 @@ dependencies: "@types/node" "*" +"@types/diff@^5.0.0": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@types/diff/-/diff-5.0.2.tgz#dd565e0086ccf8bc6522c6ebafd8a3125c91c12b" + integrity sha512-uw8eYMIReOwstQ0QKF0sICefSy8cNO/v7gOTiIy9SbwuHyEecJUm7qlgueOO5S1udZ5I/irVydHVwMchgzbKTg== + "@types/dompurify@^2.3.3": version "2.3.3" resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.3.3.tgz#c24c92f698f77ed9cc9d9fa7888f90cf2bfaa23f" @@ -2426,6 +2453,11 @@ resolved "https://registry.yarnpkg.com/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#4fc33a00c1d0c16987b1a20cf92d20614c55ac35" integrity sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg== +"@types/http-cache-semantics@*": + version "4.0.1" + resolved "https://registry.yarnpkg.com/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz#0ea7b61496902b95890dc4c3a116b60cb8dae812" + integrity sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ== + "@types/http-proxy@^1.17.8": version "1.17.9" resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.9.tgz#7f0e7931343761efde1e2bf48c40f02f3f75705a" @@ -2476,16 +2508,33 @@ jest-matcher-utils "^27.0.0" pretty-format "^27.0.0" +"@types/json-buffer@~3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/json-buffer/-/json-buffer-3.0.0.tgz#85c1ff0f0948fc159810d4b5be35bf8c20875f64" + integrity sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ== + "@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/json-stringify-safe@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@types/json-stringify-safe/-/json-stringify-safe-5.0.0.tgz#df34d054419d39323a3730966bacba02ac5e474e" + integrity sha512-UUA1sH0RSRROdInuDOA1yoRzbi5xVFD1RHCoOvNRPTNwR8zBkJ/84PZ6NhKVDtKp0FTeIccJCdQz1X2aJPr4uw== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/keyv@*": + version "3.1.4" + resolved "https://registry.yarnpkg.com/@types/keyv/-/keyv-3.1.4.tgz#3ccdb1c6751b0c7e52300bcdacd5bcbf8faa75b6" + integrity sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg== + dependencies: + "@types/node" "*" + "@types/lodash@^4.14.182": version "4.14.184" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.184.tgz#23f96cd2a21a28e106dc24d825d4aa966de7a9fe" @@ -2516,6 +2565,11 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.26.tgz#239e19f8b4ea1a9eb710528061c1d733dc561996" integrity sha512-0b+utRBSYj8L7XAp0d+DX7lI4cSmowNaaTkk6/1SKzbKkG+doLuPusB9EOvzLJ8ahJSk03bTLIL6cWaEd4dBKA== +"@types/node@^18.0.0": + version "18.7.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.14.tgz#0fe081752a3333392d00586d815485a17c2cf3c9" + integrity sha512-6bbDaETVi8oyIARulOE9qF1/Qdi/23z6emrUh0fNJRUmjznqrixD4MpGDdgOFk5Xb0m2H6Xu42JGdvAxaJR/wA== + "@types/node@^18.7.13": version "18.7.13" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.7.13.tgz#23e6c5168333480d454243378b69e861ab5c011a" @@ -2526,6 +2580,11 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz#d3357479a0fdfdd5907fe67e17e0a85c906e1301" integrity sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw== +"@types/object-inspect@^1.8.0": + version "1.8.1" + resolved "https://registry.yarnpkg.com/@types/object-inspect/-/object-inspect-1.8.1.tgz#7c08197ad05cc0e513f529b1f3919cc99f720e1f" + integrity sha512-0JTdf3CGV0oWzE6Wa40Ayv2e2GhpP3pEJMcrlM74vBSJPuuNkVwfDnl0SZxyFCXETcB4oKA/MpTVfuYSMOelBg== + "@types/parse-json@^4.0.0": version "4.0.0" resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" @@ -2644,6 +2703,13 @@ dependencies: "@types/node" "*" +"@types/responselike@*", "@types/responselike@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" + integrity sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA== + dependencies: + "@types/node" "*" + "@types/retry@0.12.0": version "0.12.0" resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" @@ -2706,6 +2772,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/supports-color@^8.1.0": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@types/supports-color/-/supports-color-8.1.1.tgz#1b44b1b096479273adf7f93c75fc4ecc40a61ee4" + integrity sha512-dPWnWsf+kzIG140B8z2w3fr5D03TLWbOAFQl45xUpI3vcizeXriNR5VYkWZ+WTMsUHqZ9Xlt3hrxGNANFyNQfw== + "@types/systemjs@^6.1.0": version "6.1.1" resolved "https://registry.yarnpkg.com/@types/systemjs/-/systemjs-6.1.1.tgz#eae17f2a080e867d01a2dd614f524ab227cf5a41" @@ -2723,6 +2794,11 @@ dependencies: "@types/jest" "*" +"@types/tmp@^0.2.0": + version "0.2.3" + resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.3.tgz#908bfb113419fd6a42273674c00994d40902c165" + integrity sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA== + "@types/trusted-types@*", "@types/trusted-types@^2.0.2": version "2.0.2" resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756" @@ -2745,6 +2821,11 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== +"@types/validator@^13.1.3": + version "13.7.6" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.7.6.tgz#631f1acd15cbac9cb0a114da7e87575f1c95b46a" + integrity sha512-uBsnWETsUagQ0n6G2wcXNIufpTNJir0zqzG4p62fhnwzs48d/iuOWEEo0d3iUxN7D+9R/8CSvWGKS+KmaD0mWA== + "@types/webpack-sources@*": version "3.2.0" resolved "https://registry.yarnpkg.com/@types/webpack-sources/-/webpack-sources-3.2.0.tgz#16d759ba096c289034b26553d2df1bf45248d38b" @@ -2900,6 +2981,52 @@ "@typescript-eslint/types" "5.34.0" eslint-visitor-keys "^3.3.0" +"@wdio/dot-reporter@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@wdio/dot-reporter/-/dot-reporter-7.24.0.tgz#4c0fe12537d38067bd015e3c3252356189d2a15a" + integrity sha512-GeeXpudVogN0wrM+1Jp6RhIKEL5/noeQljNDJr7/1Fq0KzpTNrToAzMo5OmPKqIymn+RXqI9L1lBnXWl70HoFA== + dependencies: + "@wdio/reporter" "7.24.0" + "@wdio/types" "7.24.0" + chalk "^4.0.0" + +"@wdio/junit-reporter@^7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@wdio/junit-reporter/-/junit-reporter-7.24.0.tgz#84b5027e10def7b856016d7cf3171db459e02c2d" + integrity sha512-x7R2Au30P9zscGuIlBCMfuO4wttEIGQHKc6EY12I518D46vfUKnq7iS9tqcFi7VtVMbG4irYcaDDAuSMZRyCaw== + dependencies: + "@types/json-stringify-safe" "^5.0.0" + "@types/validator" "^13.1.3" + "@wdio/reporter" "7.24.0" + "@wdio/types" "7.24.0" + json-stringify-safe "^5.0.1" + junit-report-builder "^3.0.0" + validator "^13.0.0" + +"@wdio/reporter@7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@wdio/reporter/-/reporter-7.24.0.tgz#36e26d86d57418472af7466bdaa16dc4535f3d1d" + integrity sha512-iZUQpF3zHmBH+4GWZYqf9/yDxmufSGmfIn0GkZHhiymv+pIN8y8zTNlZ5OH6t0h+ICdeoAOxlBEUBMy4eVRhHQ== + dependencies: + "@types/diff" "^5.0.0" + "@types/node" "^18.0.0" + "@types/object-inspect" "^1.8.0" + "@types/supports-color" "^8.1.0" + "@types/tmp" "^0.2.0" + "@wdio/types" "7.24.0" + diff "^5.0.0" + fs-extra "^10.0.0" + object-inspect "^1.10.3" + supports-color "8.1.1" + +"@wdio/types@7.24.0": + version "7.24.0" + resolved "https://registry.yarnpkg.com/@wdio/types/-/types-7.24.0.tgz#ded79460f8a2a4892232e2707fb8d0ce3694bfd5" + integrity sha512-wJZ+1lIHFz5aWXSO+k91wX8tfZdpyX4YYoker5xfC4zvM7ypyK81dZyiE5whS+QFL3VTCPP8dXNjwX5f5h+YEw== + dependencies: + "@types/node" "^18.0.0" + got "^11.8.1" + "@webassemblyjs/ast@1.11.1": version "1.11.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.1.tgz#2bfd767eae1a6996f432ff7e8d7fc75679c0b6a7" @@ -4469,6 +4596,24 @@ cache-base@^1.0.1: union-value "^1.0.0" unset-value "^1.0.0" +cacheable-lookup@^5.0.3: + version "5.0.4" + resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" + integrity sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA== + +cacheable-request@^7.0.2: + version "7.0.2" + resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-7.0.2.tgz#ea0d0b889364a25854757301ca12b2da77f91d27" + integrity sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew== + dependencies: + clone-response "^1.0.2" + get-stream "^5.1.0" + http-cache-semantics "^4.0.0" + keyv "^4.0.0" + lowercase-keys "^2.0.0" + normalize-url "^6.0.1" + responselike "^2.0.0" + cachedir@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-2.3.0.tgz#0c75892a052198f0b21c7c1804d8331edfcae0e8" @@ -4824,6 +4969,13 @@ cliui@^7.0.2: strip-ansi "^6.0.0" wrap-ansi "^7.0.0" +clone-response@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.3.tgz#af2032aa47816399cf5f0a1d0db902f517abb8c3" + integrity sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA== + dependencies: + mimic-response "^1.0.0" + clsx@^1.1.1: version "1.2.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.2.1.tgz#0ddc4a20a549b59c93a4116bb26f5294ca17dc12" @@ -4966,6 +5118,14 @@ compose-function@3.0.3: dependencies: arity-n "^1.0.4" +compress-brotli@^1.3.8: + version "1.3.8" + resolved "https://registry.yarnpkg.com/compress-brotli/-/compress-brotli-1.3.8.tgz#0c0a60c97a989145314ec381e84e26682e7b38db" + integrity sha512-lVcQsjhxhIXsuupfy9fmZUFtAIdBmXA7EGY6GBdgZ++qkM9zG4YFT8iU7FoBxzryNDMOpD1HIFHUSX4D87oqhQ== + dependencies: + "@types/json-buffer" "~3.0.0" + json-buffer "~3.0.1" + compressible@~2.0.16: version "2.0.18" resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba" @@ -5656,6 +5816,11 @@ date-fns@^2.0.1: resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.2.tgz#0d4b3d0f3dff0f920820a070920f0d9662c51931" integrity sha512-0VNbwmWJDS/G3ySwFSJA3ayhbURMTJLtwM2DTxf9CWondCnh6DTNlO9JgRSq6ibf4eD0lfMJNBxUdEAHHix+bA== +date-format@4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.3.tgz#f63de5dc08dc02efd8ef32bf2a6918e486f35873" + integrity sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ== + dayjs@1.10.7: version "1.10.7" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.7.tgz#2cf5f91add28116748440866a0a1d26f3a6ce468" @@ -5709,6 +5874,13 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og== +decompress-response@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" + integrity sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ== + dependencies: + mimic-response "^3.1.0" + dedent@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c" @@ -5768,6 +5940,11 @@ default-require-extensions@^3.0.0: dependencies: strip-bom "^4.0.0" +defer-to-connect@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-2.0.1.tgz#8016bdb4143e4632b77a3449c6236277de520587" + integrity sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg== + define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" @@ -5906,6 +6083,11 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== +diff@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.1.0.tgz#bc52d298c5ea8df9194800224445ed43ffc87e40" + integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== + diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -6071,36 +6253,16 @@ dot-case@^3.0.4: no-case "^3.0.4" tslib "^2.0.3" -dotenv-cli@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/dotenv-cli/-/dotenv-cli-6.0.0.tgz#8a30cbc59d0a8aaa166b2fee0a9a55e23a1223ab" - integrity sha512-qXlCOi3UMDhCWFKe0yq5sg3X+pJAz+RQDiFN38AMSbUrnY3uZshSfDJUAge951OS7J9gwLZGfsBlWRSOYz/TRg== - dependencies: - cross-spawn "^7.0.3" - dotenv "^16.0.0" - dotenv-expand "^8.0.1" - minimist "^1.2.5" - dotenv-expand@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv-expand@^8.0.1: - version "8.0.3" - resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-8.0.3.tgz#29016757455bcc748469c83a19b36aaf2b83dd6e" - integrity sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg== - dotenv@^10.0.0: version "10.0.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81" integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q== -dotenv@^16.0.0: - version "16.0.2" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.2.tgz#0b0f8652c016a3858ef795024508cddc4bffc5bf" - integrity sha512-JvpYKUmzQhYoIFgK2MOnF3bciIZoItIIoryihy0rIA+H4Jy0FmgyKYAHCTN98P5ybGSJcIFbh6QKeJdtZd1qhA== - duplexer@^0.1.1, duplexer@^0.1.2, duplexer@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" @@ -7685,6 +7847,23 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" +got@^11.8.1: + version "11.8.5" + resolved "https://registry.yarnpkg.com/got/-/got-11.8.5.tgz#ce77d045136de56e8f024bebb82ea349bc730046" + integrity sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ== + dependencies: + "@sindresorhus/is" "^4.0.0" + "@szmarczak/http-timer" "^4.0.5" + "@types/cacheable-request" "^6.0.1" + "@types/responselike" "^1.0.0" + cacheable-lookup "^5.0.3" + cacheable-request "^7.0.2" + decompress-response "^6.0.0" + http2-wrapper "^1.0.0-beta.5.2" + lowercase-keys "^2.0.0" + p-cancelable "^2.0.0" + responselike "^2.0.0" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" @@ -8006,6 +8185,11 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" +http-cache-semantics@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" + integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== + http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -8094,6 +8278,14 @@ http-signature@~1.3.6: jsprim "^2.0.2" sshpk "^1.14.1" +http2-wrapper@^1.0.0-beta.5.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/http2-wrapper/-/http2-wrapper-1.0.3.tgz#b8f55e0c1f25d4ebd08b3b0c2c079f9590800b3d" + integrity sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg== + dependencies: + quick-lru "^5.1.1" + resolve-alpn "^1.0.0" + https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" @@ -9876,6 +10068,11 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== +json-buffer@3.0.1, json-buffer@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -9970,6 +10167,24 @@ jsprim@^2.0.2: array-includes "^3.1.5" object.assign "^4.1.3" +junit-report-builder@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/junit-report-builder/-/junit-report-builder-3.0.1.tgz#8501ad56c62b801334d8903860750f699a967150" + integrity sha512-B8AZ2q24iGwPM3j/ZHc9nD0BY1rKhcnWCA1UvT8mhHfR8Vo/HTtg3ojMyo55BgctqQGZG7H8z0+g+mEUc32jgg== + dependencies: + date-format "4.0.3" + lodash "^4.17.21" + make-dir "^3.1.0" + xmlbuilder "^15.1.1" + +keyv@^4.0.0: + version "4.4.1" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.4.1.tgz#5d97bae8dfbb6788ebc9330daf5eb6582e2d3d1c" + integrity sha512-PzByhNxfBLnSBW2MZi1DF+W5+qB/7BMpOokewqIvqS8GFtP7xHm2oeGU72Y1fhtfOv/FiEnI4+nyViYDmUChnw== + dependencies: + compress-brotli "^1.3.8" + json-buffer "3.0.1" + killable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892" @@ -10338,6 +10553,11 @@ lower-case@^2.0.2: dependencies: tslib "^2.0.3" +lowercase-keys@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" + integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== + lru-cache@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" @@ -10559,6 +10779,16 @@ mimic-fn@^4.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-4.0.0.tgz#60a90550d5cb0b239cca65d893b1a53b29871ecc" integrity sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw== +mimic-response@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" + integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ== + +mimic-response@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" + integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ== + min-indent@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" @@ -10982,7 +11212,7 @@ object-hash@^3.0.0: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== -object-inspect@^1.12.0, object-inspect@^1.12.2, object-inspect@^1.9.0: +object-inspect@^1.10.3, object-inspect@^1.12.0, object-inspect@^1.12.2, object-inspect@^1.9.0: version "1.12.2" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== @@ -11162,6 +11392,11 @@ ospath@^1.2.2: resolved "https://registry.yarnpkg.com/ospath/-/ospath-1.2.2.tgz#1276639774a3f8ef2572f7fe4280e0ea4550c07b" integrity sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA== +p-cancelable@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf" + integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg== + p-each-series@^2.1.0: version "2.2.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" @@ -13199,6 +13434,11 @@ resize-observer-polyfill@1.5.0: resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.0.tgz#660ff1d9712a2382baa2cad450a4716209f9ca69" integrity sha512-M2AelyJDVR/oLnToJLtuDJRBBWUGUvvGigj1411hXhAdyFWqMaqHp7TixW3FpiLuVaikIcR1QL+zqoJoZlOgpg== +resolve-alpn@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/resolve-alpn/-/resolve-alpn-1.2.1.tgz#b7adbdac3546aaaec20b45e7d8265927072726f9" + integrity sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g== + resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" @@ -13296,6 +13536,13 @@ resolve@^2.0.0-next.3: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +responselike@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/responselike/-/responselike-2.0.1.tgz#9a0bc8fdc252f3fb1cca68b016591059ba1422bc" + integrity sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw== + dependencies: + lowercase-keys "^2.0.0" + restore-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e" @@ -14399,6 +14646,13 @@ stylis@4.0.13: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag== +supports-color@8.1.1, supports-color@^8.0.0, supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -14432,13 +14686,6 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@^8.0.0, supports-color@^8.1.1: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - supports-hyperlinks@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz#4f77b42488765891774b70c79babd87f9bd594bb" @@ -15292,6 +15539,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +validator@^13.0.0: + version "13.7.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857" + integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw== + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -16018,6 +16270,11 @@ xml-name-validator@^3.0.0: resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw== +xmlbuilder@^15.1.1: + version "15.1.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5" + integrity sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg== + xmlchars@^2.1.1, xmlchars@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" From 3e97d80069c79cef376e4bf63e7e181e034166ff Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Fri, 2 Sep 2022 11:07:33 +0800 Subject: [PATCH 005/113] success tests --- cypress/e2e/home/home.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/e2e/home/home.spec.ts b/cypress/e2e/home/home.spec.ts index 960cda452..43c584180 100644 --- a/cypress/e2e/home/home.spec.ts +++ b/cypress/e2e/home/home.spec.ts @@ -6,7 +6,7 @@ describe('Landing Page', () => { cy.get('[data-id="root"]').should('be.visible') }) - it('loads landing page should fail', () => { + it.skip('loads landing page should fail', () => { cy.get('[data-id="root"]').should('not.be.visible') }) }) From 37d0a32183dea45ca3a29a54fb7333ab1b27a9c8 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Fri, 2 Sep 2022 11:18:53 +0800 Subject: [PATCH 006/113] integrate test report --- .circleci/config.yml | 4 ++-- .gitignore | 2 +- cypress.config.ts | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 79394a923..ccbd83edc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -84,9 +84,9 @@ build_steps: &build_steps - run: *build_configuration_fetch - run: *running_yarn_build - store_test_results: - path: test-results.xml + path: cypress/test-report - store_artifacts: - path: test-results.xml + path: cypress/test-report - store_artifacts: path: cypress/videos - store_artifacts: diff --git a/.gitignore b/.gitignore index 4ac1f11aa..6d23f4087 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ .nyc_output /cypress/screenshots /cypress/videos -test-results.xml +/cypress/test-report # production /build diff --git a/cypress.config.ts b/cypress.config.ts index 82fbc1a8f..b0b0280a4 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -15,5 +15,10 @@ export default defineConfig({ require('@cypress/code-coverage/task')(on, config) return config; }, + }, + reporter: 'junit', + reporterOptions: { + mochaFile: 'cypress/test-report/test-result-[hash].xml', + toConsole: false } }) From 0840b7c3cdb0156990b00d3425fbb0e5ffcd581e Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Fri, 2 Sep 2022 11:28:26 +0800 Subject: [PATCH 007/113] test with failure case --- cypress/e2e/home/home.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/e2e/home/home.spec.ts b/cypress/e2e/home/home.spec.ts index 43c584180..960cda452 100644 --- a/cypress/e2e/home/home.spec.ts +++ b/cypress/e2e/home/home.spec.ts @@ -6,7 +6,7 @@ describe('Landing Page', () => { cy.get('[data-id="root"]').should('be.visible') }) - it.skip('loads landing page should fail', () => { + it('loads landing page should fail', () => { cy.get('[data-id="root"]').should('not.be.visible') }) }) From 7aff7c10912fb41081e350123a0860f8b21474c3 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Fri, 2 Sep 2022 19:29:13 +0800 Subject: [PATCH 008/113] skip failed e2e test cases --- cypress/e2e/home/home.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/e2e/home/home.spec.ts b/cypress/e2e/home/home.spec.ts index 960cda452..43c584180 100644 --- a/cypress/e2e/home/home.spec.ts +++ b/cypress/e2e/home/home.spec.ts @@ -6,7 +6,7 @@ describe('Landing Page', () => { cy.get('[data-id="root"]').should('be.visible') }) - it('loads landing page should fail', () => { + it.skip('loads landing page should fail', () => { cy.get('[data-id="root"]').should('not.be.visible') }) }) From e4f43054d450c3cd1b9859520e6c6d87052225b1 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Fri, 2 Sep 2022 19:30:55 +0800 Subject: [PATCH 009/113] finalize yarn.lock file --- yarn.lock | 9 --------- 1 file changed, 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index 32d30477e..161261927 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2981,15 +2981,6 @@ "@typescript-eslint/types" "5.34.0" eslint-visitor-keys "^3.3.0" -"@wdio/dot-reporter@^7.24.0": - version "7.24.0" - resolved "https://registry.yarnpkg.com/@wdio/dot-reporter/-/dot-reporter-7.24.0.tgz#4c0fe12537d38067bd015e3c3252356189d2a15a" - integrity sha512-GeeXpudVogN0wrM+1Jp6RhIKEL5/noeQljNDJr7/1Fq0KzpTNrToAzMo5OmPKqIymn+RXqI9L1lBnXWl70HoFA== - dependencies: - "@wdio/reporter" "7.24.0" - "@wdio/types" "7.24.0" - chalk "^4.0.0" - "@wdio/junit-reporter@^7.24.0": version "7.24.0" resolved "https://registry.yarnpkg.com/@wdio/junit-reporter/-/junit-reporter-7.24.0.tgz#84b5027e10def7b856016d7cf3171db459e02c2d" From a1abe2f47f1a7784c54e5970be5ce99639ef0c5e Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Mon, 5 Sep 2022 10:40:57 +0800 Subject: [PATCH 010/113] try workflow --- .github/workflows/config.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/config.yml diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml new file mode 100644 index 000000000..03f8261bf --- /dev/null +++ b/.github/workflows/config.yml @@ -0,0 +1,24 @@ +name: Node CI + +on: [push] + +jobs: + build_and_test: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: 16.14.0 + steps: + - uses: actions/checkout@v3 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v3 + with: + node-version: ${{ matrix.node-version }} + - run: | + apt update + apt install jq python3-pip libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb -y + pip3 install awscli --upgrade + yarn install + yarn cypress install + yarn build + yarn cy:ci From d7eb09e47bf18d69cabfb1058e89ca7a45515c6b Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Mon, 5 Sep 2022 10:42:29 +0800 Subject: [PATCH 011/113] correct action --- .github/workflows/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 03f8261bf..4660cf9ea 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: 16.14.0 + node-version: [16.14.0] steps: - uses: actions/checkout@v3 - name: Use Node.js ${{ matrix.node-version }} From 844928f6f65496213ca1fa723e7fac1d54bf54e3 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Mon, 5 Sep 2022 10:43:33 +0800 Subject: [PATCH 012/113] add sudo in lib installation --- .github/workflows/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 4660cf9ea..304517a45 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -15,9 +15,9 @@ jobs: with: node-version: ${{ matrix.node-version }} - run: | - apt update - apt install jq python3-pip libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb -y - pip3 install awscli --upgrade + sudo apt update + sudo apt install jq python3-pip libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb -y + sudo pip3 install awscli --upgrade yarn install yarn cypress install yarn build From c5efbb5b9c6d5e7a21e1ce5fb3fe91d2af89fec8 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Mon, 5 Sep 2022 10:48:18 +0800 Subject: [PATCH 013/113] store testing result --- .github/workflows/config.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 304517a45..fceb805b2 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -22,3 +22,11 @@ jobs: yarn cypress install yarn build yarn cy:ci + - name: Storing test artifcats + uses: actions/upload-artifcat@v3 + with: + name: store-testing-result + path: | + cypress/test-report + cypress/videos + cypress/screenshots From 648375b4c87257cc4e4fb800e7e3397bec099b18 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Mon, 5 Sep 2022 10:51:07 +0800 Subject: [PATCH 014/113] correct command --- .github/workflows/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index fceb805b2..0752a9eab 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -22,8 +22,8 @@ jobs: yarn cypress install yarn build yarn cy:ci - - name: Storing test artifcats - uses: actions/upload-artifcat@v3 + - name: Storing test artifacts + uses: actions/upload-artifact@v3 with: name: store-testing-result path: | From a77f304d3f54cf655ca19adc697b44ee8060cd76 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Mon, 5 Sep 2022 12:02:47 +0800 Subject: [PATCH 015/113] check failed test case --- cypress/e2e/home/home.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/e2e/home/home.spec.ts b/cypress/e2e/home/home.spec.ts index 43c584180..960cda452 100644 --- a/cypress/e2e/home/home.spec.ts +++ b/cypress/e2e/home/home.spec.ts @@ -6,7 +6,7 @@ describe('Landing Page', () => { cy.get('[data-id="root"]').should('be.visible') }) - it.skip('loads landing page should fail', () => { + it('loads landing page should fail', () => { cy.get('[data-id="root"]').should('not.be.visible') }) }) From 893415437e35d8cd504cd8bc32266b07d2a0ef7c Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Mon, 5 Sep 2022 12:09:53 +0800 Subject: [PATCH 016/113] continue on error --- .github/workflows/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index 0752a9eab..f57c663af 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -24,6 +24,7 @@ jobs: yarn cy:ci - name: Storing test artifacts uses: actions/upload-artifact@v3 + continue-on-error: true with: name: store-testing-result path: | From 629c9338c485ee91548d55901325895095fe5573 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Mon, 5 Sep 2022 12:18:35 +0800 Subject: [PATCH 017/113] enable cache operation --- .github/workflows/config.yml | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index f57c663af..e5134ffae 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -18,13 +18,29 @@ jobs: sudo apt update sudo apt install jq python3-pip libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb -y sudo pip3 install awscli --upgrade + - name: Cache data + id: cache-data + uses: actions/cache@v2 + with: + path: | + node_modules + ~/.cache/Cypress + key: ${{ runner.os }}-build-${{ hashFiles('**/yarn-lock.json') }} + restore-keys: | + ${{ runner.os }}-build- + ${{ runner.os }}- + - name: Install Dependencies + if: steps.cache-data.outputs.cache-hit != 'true' + run: | yarn install - yarn cypress install + yarn cypress + - name: Build the codebase and Run E2E Tests + continue-on-error: true + run: | yarn build yarn cy:ci - name: Storing test artifacts uses: actions/upload-artifact@v3 - continue-on-error: true with: name: store-testing-result path: | From f757bf27be6f9dd151d1c5c39dfc766cd261d1f8 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Mon, 5 Sep 2022 12:21:25 +0800 Subject: [PATCH 018/113] yarn cache id update --- .github/workflows/config.yml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml index e5134ffae..31c58dd78 100644 --- a/.github/workflows/config.yml +++ b/.github/workflows/config.yml @@ -19,18 +19,17 @@ jobs: sudo apt install jq python3-pip libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb -y sudo pip3 install awscli --upgrade - name: Cache data - id: cache-data + id: yarn-cache uses: actions/cache@v2 with: path: | node_modules ~/.cache/Cypress - key: ${{ runner.os }}-build-${{ hashFiles('**/yarn-lock.json') }} + key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} restore-keys: | - ${{ runner.os }}-build- - ${{ runner.os }}- + ${{ runner.os }}-yarn- - name: Install Dependencies - if: steps.cache-data.outputs.cache-hit != 'true' + if: steps.yarn-cache.outputs.cache-hit != 'true' run: | yarn install yarn cypress From 550330f4da07a6aa7515937557f5a69b72fc624c Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 7 Sep 2022 18:25:37 +0300 Subject: [PATCH 019/113] TCA-400 - update behavior for fcc sidebar (course outline navigation) --- .../CertificateBgPattern.module.scss | 4 +- .../free-code-camp/FreeCodeCamp.module.scss | 29 ----------- .../learn/free-code-camp/FreeCodeCamp.tsx | 28 ++++------- .../fcc-sidebar/FccSidebar.module.scss | 31 ++++++++++++ .../free-code-camp/fcc-sidebar/FccSidebar.tsx | 48 +++++++++++++++++++ .../learn/free-code-camp/fcc-sidebar/index.ts | 1 + .../collapsible-pane/CollapsiblePane.tsx | 38 ++++++++++++++- .../course-outline/CourseOutline.tsx | 2 + .../collapsible-item/CollapsibleItem.tsx | 2 + 9 files changed, 130 insertions(+), 53 deletions(-) create mode 100644 src-ts/tools/learn/free-code-camp/fcc-sidebar/FccSidebar.module.scss create mode 100644 src-ts/tools/learn/free-code-camp/fcc-sidebar/FccSidebar.tsx create mode 100644 src-ts/tools/learn/free-code-camp/fcc-sidebar/index.ts diff --git a/src-ts/tools/learn/course-certificate/certificate-view/certificate/certificate-bg-pattern/CertificateBgPattern.module.scss b/src-ts/tools/learn/course-certificate/certificate-view/certificate/certificate-bg-pattern/CertificateBgPattern.module.scss index 2075827eb..78fdf28b0 100644 --- a/src-ts/tools/learn/course-certificate/certificate-view/certificate/certificate-bg-pattern/CertificateBgPattern.module.scss +++ b/src-ts/tools/learn/course-certificate/certificate-view/certificate/certificate-bg-pattern/CertificateBgPattern.module.scss @@ -31,7 +31,7 @@ > div { position: absolute; top: 0; - left: 0; + left: -1px; width: 100%; height: 100%; z-index: 1; @@ -42,7 +42,7 @@ } &:global(.wave-bg) { - background: url('./wave-bg.png') -1px 0 repeat-y; + background: url('./wave-bg.png') 0 0 repeat-y; background-size: 400px 116px; } } diff --git a/src-ts/tools/learn/free-code-camp/FreeCodeCamp.module.scss b/src-ts/tools/learn/free-code-camp/FreeCodeCamp.module.scss index abf59cf76..4be0344b3 100644 --- a/src-ts/tools/learn/free-code-camp/FreeCodeCamp.module.scss +++ b/src-ts/tools/learn/free-code-camp/FreeCodeCamp.module.scss @@ -48,32 +48,3 @@ } } } - -.course-outline-pane { - position: absolute; - left: 0; - top: 0; - bottom: 0; - - @include ltemd { - position: relative; - top: auto; - left: auto; - bottom: auto; - flex: 0 0 auto; - } -} - -.course-outline-wrap { - width: 406px; - - @include ltemd { - width: 100%; - } -} - -.course-outline-title { - @extend .body-main-bold; - flex: 0 0 auto; - margin-bottom: $space-xl; -} \ No newline at end of file diff --git a/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx b/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx index 7552abd1f..9f123afc7 100644 --- a/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx +++ b/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx @@ -19,8 +19,6 @@ import { ProfileContextData, } from '../../../lib' import { - CollapsiblePane, - CourseOutline, CoursesProviderData, LearnLesson, LearnModule, @@ -39,6 +37,7 @@ import { import { getCertificationCompletedPath, getCoursePath, getLessonPathFromModule } from '../learn.routes' import { FccFrame } from './fcc-frame' +import { FccSidebar } from './fcc-sidebar' import styles from './FreeCodeCamp.module.scss' import { TitleNav } from './title-nav' @@ -362,24 +361,13 @@ const FreeCodeCamp: FC<{}> = () => { {lesson && (
-
- isOpen && refetchProgress()} - > -
-
- {courseData?.title} -
- -
-
-
+
void +} + +const FccSidebar: FC = (props: FccSidebarProps) => { + const [isOpen, setIsOpen] = useState(false); + + const handleToggle = (isOutlineOpen: boolean) => { + setIsOpen(isOutlineOpen) + if (isOutlineOpen) { + props.refetchProgress() + } + } + + return ( +
+ +
+
+ {props.courseData?.title} +
+ setIsOpen(false)} + /> +
+
+
+ ) +} + +export default FccSidebar diff --git a/src-ts/tools/learn/free-code-camp/fcc-sidebar/index.ts b/src-ts/tools/learn/free-code-camp/fcc-sidebar/index.ts new file mode 100644 index 000000000..5f95796fc --- /dev/null +++ b/src-ts/tools/learn/free-code-camp/fcc-sidebar/index.ts @@ -0,0 +1 @@ +export { default as FccSidebar } from './FccSidebar' diff --git a/src-ts/tools/learn/learn-lib/collapsible-pane/CollapsiblePane.tsx b/src-ts/tools/learn/learn-lib/collapsible-pane/CollapsiblePane.tsx index e73399a51..ad7fe658c 100644 --- a/src-ts/tools/learn/learn-lib/collapsible-pane/CollapsiblePane.tsx +++ b/src-ts/tools/learn/learn-lib/collapsible-pane/CollapsiblePane.tsx @@ -1,6 +1,16 @@ +import { + Dispatch, + FC, + ReactNode, + SetStateAction, + MutableRefObject, + useCallback, + useEffect, + useState, + useRef, +} from 'react' import classNames from 'classnames' import { noop } from 'lodash' -import { Dispatch, FC, ReactNode, SetStateAction, useCallback, useState } from 'react' import { IconSolid } from '../../../../lib' @@ -11,19 +21,43 @@ interface CollapsiblePaneProps { onToggle?: (isOpen: boolean) => void position?: 'to-left'|'to-right' title: string + isOpen?: boolean } const CollapsiblePane: FC = (props: CollapsiblePaneProps) => { const {onToggle = noop}: CollapsiblePaneProps = props const [isOpen, setIsOpen]: [boolean, Dispatch>] = useState(false) + const elRef: MutableRefObject = useRef() + const toggle: () => void = useCallback(() => { setIsOpen(!isOpen) onToggle(!isOpen) }, [isOpen, onToggle]) + const close = useCallback(() => { + setIsOpen(false) + onToggle(false) + }, [onToggle]) + + useEffect(() => { + setIsOpen(!!props.isOpen) + }, [props.isOpen]) + + useEffect(() => { + const handleClickOutside = (ev: MouseEvent) => { + if (elRef.current && !elRef.current.contains(ev.target)) { + close() + } + } + if (isOpen) { + document.addEventListener('click', handleClickOutside) + } + return () => document.removeEventListener('click', handleClickOutside) + }, [close]) + return ( -
void } const CourseOutline: FC = (props: CourseOutlineProps) => { @@ -52,6 +53,7 @@ const CourseOutline: FC = (props: CourseOutlineProps) => { progress={props.progress?.modules} shortDescription={module.meta.introCopy} title={module.meta.name} + onItemClick={props.onItemNavigate} /> ))}
diff --git a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx index 2d91608c2..82904367d 100644 --- a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx +++ b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx @@ -25,6 +25,7 @@ interface CollapsibleItemProps { progress?: LearnUserCertificationProgress['modules'] shortDescription: Array title: string + onItemClick: (item: any) => void } const CollapsibleItem: FC = (props: CollapsibleItemProps) => { @@ -72,6 +73,7 @@ const CollapsibleItem: FC = (props: CollapsibleItemProps)
  • props.onItemClick(item)} > {props.path ? ( From 97d9287bbe7cac9318ac0f6083b0df9c0d6e4711 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 7 Sep 2022 18:47:02 +0300 Subject: [PATCH 020/113] lint --- .../free-code-camp/fcc-sidebar/FccSidebar.tsx | 7 ++++--- .../collapsible-pane/CollapsiblePane.tsx | 16 ++++++++-------- .../learn-lib/course-outline/CourseOutline.tsx | 2 +- .../collapsible-item/CollapsibleItem.tsx | 2 +- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src-ts/tools/learn/free-code-camp/fcc-sidebar/FccSidebar.tsx b/src-ts/tools/learn/free-code-camp/fcc-sidebar/FccSidebar.tsx index 6d53097b4..c298575f2 100644 --- a/src-ts/tools/learn/free-code-camp/fcc-sidebar/FccSidebar.tsx +++ b/src-ts/tools/learn/free-code-camp/fcc-sidebar/FccSidebar.tsx @@ -1,4 +1,5 @@ import { FC, useState } from 'react' + import { CollapsiblePane, CourseOutline, LearnCourse, LearnUserCertificationProgress } from '../../learn-lib' import styles from './FccSidebar.module.scss' @@ -12,15 +13,15 @@ interface FccSidebarProps { } const FccSidebar: FC = (props: FccSidebarProps) => { - const [isOpen, setIsOpen] = useState(false); + const [isOpen, setIsOpen]: [boolean, Dispatch>] = useState(false) - const handleToggle = (isOutlineOpen: boolean) => { + const handleToggle: (isOutlineOpen: boolean) => void = (isOutlineOpen: boolean) => { setIsOpen(isOutlineOpen) if (isOutlineOpen) { props.refetchProgress() } } - + return (
    void position?: 'to-left'|'to-right' title: string - isOpen?: boolean } const CollapsiblePane: FC = (props: CollapsiblePaneProps) => { @@ -35,7 +35,7 @@ const CollapsiblePane: FC = (props: CollapsiblePaneProps) onToggle(!isOpen) }, [isOpen, onToggle]) - const close = useCallback(() => { + const close: () => void = useCallback(() => { setIsOpen(false) onToggle(false) }, [onToggle]) @@ -45,7 +45,7 @@ const CollapsiblePane: FC = (props: CollapsiblePaneProps) }, [props.isOpen]) useEffect(() => { - const handleClickOutside = (ev: MouseEvent) => { + const handleClickOutside: (ev: MouseEvent) => void = (ev: MouseEvent) => { if (elRef.current && !elRef.current.contains(ev.target)) { close() } @@ -54,7 +54,7 @@ const CollapsiblePane: FC = (props: CollapsiblePaneProps) document.addEventListener('click', handleClickOutside) } return () => document.removeEventListener('click', handleClickOutside) - }, [close]) + }, [close, isOpen]) return (
    void progress?: LearnUserCertificationProgress ready?: boolean - onItemNavigate: (item: LearnLesson) => void } const CourseOutline: FC = (props: CourseOutlineProps) => { diff --git a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx index 82904367d..cac0bd7fc 100644 --- a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx +++ b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx @@ -21,11 +21,11 @@ interface CollapsibleItemProps { items: Array lessonsCount: number moduleKey: string + onItemClick: (item: any) => void path?: (item: any) => string progress?: LearnUserCertificationProgress['modules'] shortDescription: Array title: string - onItemClick: (item: any) => void } const CollapsibleItem: FC = (props: CollapsibleItemProps) => { From 5cf3d8f68627b6e972f8c5aac175521c93ecece8 Mon Sep 17 00:00:00 2001 From: Brooke Date: Wed, 7 Sep 2022 11:47:19 -0700 Subject: [PATCH 021/113] TCA-404 #comment This commit tweaks for formatting of the cypress config file for linting. It also adds cypress types and a declaration for proper linting. #time 30m --- cypress.config.ts | 41 ++++++++++++++++-------------- package.json | 7 ++--- src-ts/declarations.d.ts | 2 ++ yarn.lock | 55 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 22 deletions(-) diff --git a/cypress.config.ts b/cypress.config.ts index b0b0280a4..9c399bd54 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,24 +1,27 @@ +// tslint:disable-next-line: no-submodule-imports This is the way cypress does it +import task from '@cypress/code-coverage/task' import { defineConfig } from 'cypress' export default defineConfig({ - fixturesFolder: false, - video: true, - screenshotOnRunFailure: true, - defaultCommandTimeout: 10000, - e2e: { - baseUrl: 'http://localhost:3000', - specPattern: "cypress/e2e/**/*.spec.{js,jsx,ts,tsx}", - supportFile: "cypress/support/e2e.ts", - viewportHeight: 1000, - viewportWidth: 1280, - setupNodeEvents(on, config) { - require('@cypress/code-coverage/task')(on, config) - return config; + defaultCommandTimeout: 10000, + e2e: { + // baseUrl: 'https://local.topcoder-dev.com', + baseUrl: 'http://localhost:3000', + setupNodeEvents(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions): Cypress.PluginConfigOptions { + task(on, config) + return config + }, + specPattern: 'cypress/e2e/**/*.spec.{js,jsx,ts,tsx}', + supportFile: 'cypress/support/e2e.ts', + viewportHeight: 1000, + viewportWidth: 1280, }, - }, - reporter: 'junit', - reporterOptions: { - mochaFile: 'cypress/test-report/test-result-[hash].xml', - toConsole: false - } + fixturesFolder: false, + reporter: 'junit', + reporterOptions: { + mochaFile: 'cypress/test-report/test-result-[hash].xml', + toConsole: false, + }, + screenshotOnRunFailure: true, + video: true, }) diff --git a/package.json b/package.json index 657094294..d17a1b4b5 100644 --- a/package.json +++ b/package.json @@ -23,9 +23,6 @@ "dependencies": { "@datadog/browser-logs": "^4.7.1", "@heroicons/react": "^1.0.6", - "@types/dompurify": "^2.3.3", - "@types/highlightjs": "^9.12.2", - "@types/marked": "4.0.3", "apexcharts": "^3.35.3", "axios": "^0.26.1", "browser-cookies": "^1.2.0", @@ -82,8 +79,12 @@ "@testing-library/react": "^12.0.0", "@testing-library/user-event": "^13.2.1", "@types/axios": "^0.14.0", + "@types/cypress": "^1.1.3", + "@types/dompurify": "^2.3.3", + "@types/highlightjs": "^9.12.2", "@types/jest": "^27.0.1", "@types/lodash": "^4.14.182", + "@types/marked": "4.0.3", "@types/node": "^18.7.13", "@types/reach__router": "^1.3.10", "@types/react": "^18.0.5", diff --git a/src-ts/declarations.d.ts b/src-ts/declarations.d.ts index bd7a8df0c..84ecabc9c 100644 --- a/src-ts/declarations.d.ts +++ b/src-ts/declarations.d.ts @@ -1,3 +1,5 @@ +declare module '@cypress/code-coverage/task' + declare module '*.html' { const htmlFile: string export = htmlFile diff --git a/yarn.lock b/yarn.lock index 161261927..7f848882a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2348,6 +2348,13 @@ dependencies: "@types/node" "*" +"@types/cypress@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@types/cypress/-/cypress-1.1.3.tgz#0a700c040d53e9e12b5af98e41d4a88c39f39b6a" + integrity sha512-OXe0Gw8LeCflkG1oPgFpyrYWJmEKqYncBsD/J0r17r0ETx/TnIGDNLwXt/pFYSYuYTpzcq1q3g62M9DrfsBL4g== + dependencies: + cypress "*" + "@types/diff@^5.0.0": version "5.0.2" resolved "https://registry.yarnpkg.com/@types/diff/-/diff-5.0.2.tgz#dd565e0086ccf8bc6522c6ebafd8a3125c91c12b" @@ -5716,6 +5723,54 @@ cyclist@^1.0.1: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A== +cypress@*: + version "10.7.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-10.7.0.tgz#2d37f8b9751c6de33ee48639cb7e67a2ce593231" + integrity sha512-gTFvjrUoBnqPPOu9Vl5SBHuFlzx/Wxg/ZXIz2H4lzoOLFelKeF7mbwYUOzgzgF0oieU2WhJAestQdkgwJMMTvQ== + dependencies: + "@cypress/request" "^2.88.10" + "@cypress/xvfb" "^1.2.4" + "@types/node" "^14.14.31" + "@types/sinonjs__fake-timers" "8.1.1" + "@types/sizzle" "^2.3.2" + arch "^2.2.0" + blob-util "^2.0.2" + bluebird "^3.7.2" + buffer "^5.6.0" + cachedir "^2.3.0" + chalk "^4.1.0" + check-more-types "^2.24.0" + cli-cursor "^3.1.0" + cli-table3 "~0.6.1" + commander "^5.1.0" + common-tags "^1.8.0" + dayjs "^1.10.4" + debug "^4.3.2" + enquirer "^2.3.6" + eventemitter2 "^6.4.3" + execa "4.1.0" + executable "^4.1.1" + extract-zip "2.0.1" + figures "^3.2.0" + fs-extra "^9.1.0" + getos "^3.2.1" + is-ci "^3.0.0" + is-installed-globally "~0.4.0" + lazy-ass "^1.6.0" + listr2 "^3.8.3" + lodash "^4.17.21" + log-symbols "^4.0.0" + minimist "^1.2.6" + ospath "^1.2.2" + pretty-bytes "^5.6.0" + proxy-from-env "1.0.0" + request-progress "^3.0.0" + semver "^7.3.2" + supports-color "^8.1.1" + tmp "~0.2.1" + untildify "^4.0.0" + yauzl "^2.10.0" + cypress@^10.6.0: version "10.6.0" resolved "https://registry.yarnpkg.com/cypress/-/cypress-10.6.0.tgz#13f46867febf2c3715874ed5dce9c2e946b175fe" From 11fb137c871a83f56f5e6f5fe5544dc9a2e64727 Mon Sep 17 00:00:00 2001 From: Brooke Date: Wed, 7 Sep 2022 11:58:37 -0700 Subject: [PATCH 022/113] TCA-404 #comment This commit does a little bit more clean-up #time 10m --- cypress.config.ts | 17 +++++++++++------ .../authentication.functions.test.ts | 6 ------ .../user-functions/user.functions.test.ts | 8 -------- 3 files changed, 11 insertions(+), 20 deletions(-) delete mode 100644 src-ts/lib/functions/authentication-functions/authentication.functions.test.ts delete mode 100644 src-ts/lib/functions/user-functions/user.functions.test.ts diff --git a/cypress.config.ts b/cypress.config.ts index 9c399bd54..7a33104af 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -5,12 +5,9 @@ import { defineConfig } from 'cypress' export default defineConfig({ defaultCommandTimeout: 10000, e2e: { - // baseUrl: 'https://local.topcoder-dev.com', - baseUrl: 'http://localhost:3000', - setupNodeEvents(on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions): Cypress.PluginConfigOptions { - task(on, config) - return config - }, + baseUrl: 'https://local.topcoder-dev.com', + // baseUrl: 'http://localhost:3000', + setupNodeEvents: setUpNodeEvents, specPattern: 'cypress/e2e/**/*.spec.{js,jsx,ts,tsx}', supportFile: 'cypress/support/e2e.ts', viewportHeight: 1000, @@ -25,3 +22,11 @@ export default defineConfig({ screenshotOnRunFailure: true, video: true, }) + +function setUpNodeEvents( + on: Cypress.PluginEvents, + config: Cypress.PluginConfigOptions +): Cypress.PluginConfigOptions { + task(on, config) + return config +} diff --git a/src-ts/lib/functions/authentication-functions/authentication.functions.test.ts b/src-ts/lib/functions/authentication-functions/authentication.functions.test.ts deleted file mode 100644 index 8d3b4e806..000000000 --- a/src-ts/lib/functions/authentication-functions/authentication.functions.test.ts +++ /dev/null @@ -1,6 +0,0 @@ -import '@testing-library/jest-dom' - -describe('Authentication Functions', () => { - - test('authentication', () => { }) -}) diff --git a/src-ts/lib/functions/user-functions/user.functions.test.ts b/src-ts/lib/functions/user-functions/user.functions.test.ts deleted file mode 100644 index c77886f77..000000000 --- a/src-ts/lib/functions/user-functions/user.functions.test.ts +++ /dev/null @@ -1,8 +0,0 @@ -import '@testing-library/jest-dom' - -describe('Profile Functions', () => { - - test('TODO', () => { - - }) -}) From f95d26fcffeed6c8eb7fb2067b354a323f685d5a Mon Sep 17 00:00:00 2001 From: Brooke Date: Wed, 7 Sep 2022 12:21:20 -0700 Subject: [PATCH 023/113] TCA-404 #comment This comment removes extraneous code that was in the v6 branch but which we don't want to merge to dev #time 10m --- .github/workflows/config.yml | 48 ----------------------------------- cypress/e2e/home/home.spec.ts | 2 +- 2 files changed, 1 insertion(+), 49 deletions(-) delete mode 100644 .github/workflows/config.yml diff --git a/.github/workflows/config.yml b/.github/workflows/config.yml deleted file mode 100644 index 31c58dd78..000000000 --- a/.github/workflows/config.yml +++ /dev/null @@ -1,48 +0,0 @@ -name: Node CI - -on: [push] - -jobs: - build_and_test: - runs-on: ubuntu-latest - strategy: - matrix: - node-version: [16.14.0] - steps: - - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 - with: - node-version: ${{ matrix.node-version }} - - run: | - sudo apt update - sudo apt install jq python3-pip libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb -y - sudo pip3 install awscli --upgrade - - name: Cache data - id: yarn-cache - uses: actions/cache@v2 - with: - path: | - node_modules - ~/.cache/Cypress - key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: | - ${{ runner.os }}-yarn- - - name: Install Dependencies - if: steps.yarn-cache.outputs.cache-hit != 'true' - run: | - yarn install - yarn cypress - - name: Build the codebase and Run E2E Tests - continue-on-error: true - run: | - yarn build - yarn cy:ci - - name: Storing test artifacts - uses: actions/upload-artifact@v3 - with: - name: store-testing-result - path: | - cypress/test-report - cypress/videos - cypress/screenshots diff --git a/cypress/e2e/home/home.spec.ts b/cypress/e2e/home/home.spec.ts index 960cda452..43c584180 100644 --- a/cypress/e2e/home/home.spec.ts +++ b/cypress/e2e/home/home.spec.ts @@ -6,7 +6,7 @@ describe('Landing Page', () => { cy.get('[data-id="root"]').should('be.visible') }) - it('loads landing page should fail', () => { + it.skip('loads landing page should fail', () => { cy.get('[data-id="root"]').should('not.be.visible') }) }) From 33952f5f146981c9b18936845f50f591d704570b Mon Sep 17 00:00:00 2001 From: Brooke Date: Wed, 7 Sep 2022 12:35:26 -0700 Subject: [PATCH 024/113] TCA-404 #comment This commit fixes the base url in source control #time 5m --- src-ts/tools/learn/learn-config/learn.bsouza.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-ts/tools/learn/learn-config/learn.bsouza.config.ts b/src-ts/tools/learn/learn-config/learn.bsouza.config.ts index ab591ccaa..a337ba971 100644 --- a/src-ts/tools/learn/learn-config/learn.bsouza.config.ts +++ b/src-ts/tools/learn/learn-config/learn.bsouza.config.ts @@ -4,6 +4,6 @@ import { LearnConfigDev } from './learn.dev.config' export const LearnConfigBsouza: LearnConfigModel = { ...LearnConfigDev, - // API: LearnConfigDefault.API, + API: LearnConfigDefault.API, CLIENT: LearnConfigDefault.CLIENT, } From b835cfe15ec8cedf0b643c129ad232afaa111f06 Mon Sep 17 00:00:00 2001 From: Brooke Date: Wed, 7 Sep 2022 12:35:45 -0700 Subject: [PATCH 025/113] TCA-404 fix base url --- cypress.config.ts | 4 ++-- src-ts/tools/learn/learn-config/learn.bsouza.config.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cypress.config.ts b/cypress.config.ts index 7a33104af..97651723a 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -5,8 +5,8 @@ import { defineConfig } from 'cypress' export default defineConfig({ defaultCommandTimeout: 10000, e2e: { - baseUrl: 'https://local.topcoder-dev.com', - // baseUrl: 'http://localhost:3000', + // baseUrl: 'https://local.topcoder-dev.com', + baseUrl: 'http://localhost:3000', setupNodeEvents: setUpNodeEvents, specPattern: 'cypress/e2e/**/*.spec.{js,jsx,ts,tsx}', supportFile: 'cypress/support/e2e.ts', diff --git a/src-ts/tools/learn/learn-config/learn.bsouza.config.ts b/src-ts/tools/learn/learn-config/learn.bsouza.config.ts index a337ba971..58b0b8dd1 100644 --- a/src-ts/tools/learn/learn-config/learn.bsouza.config.ts +++ b/src-ts/tools/learn/learn-config/learn.bsouza.config.ts @@ -4,6 +4,6 @@ import { LearnConfigDev } from './learn.dev.config' export const LearnConfigBsouza: LearnConfigModel = { ...LearnConfigDev, - API: LearnConfigDefault.API, - CLIENT: LearnConfigDefault.CLIENT, + // API: LearnConfigDefault.API, + // CLIENT: LearnConfigDefault.CLIENT, } From ea43c8a4bdb1fe28844b13dad55823f8ca084639 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 7 Sep 2022 22:55:54 +0300 Subject: [PATCH 026/113] TCA-400 - use the clickoutside hook --- .../learn-lib/collapsible-pane/CollapsiblePane.tsx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src-ts/tools/learn/learn-lib/collapsible-pane/CollapsiblePane.tsx b/src-ts/tools/learn/learn-lib/collapsible-pane/CollapsiblePane.tsx index 44788aa3b..dadf18d74 100644 --- a/src-ts/tools/learn/learn-lib/collapsible-pane/CollapsiblePane.tsx +++ b/src-ts/tools/learn/learn-lib/collapsible-pane/CollapsiblePane.tsx @@ -12,7 +12,7 @@ import { useState, } from 'react' -import { IconSolid } from '../../../../lib' +import { IconSolid, useClickOutside } from '../../../../lib' import styles from './CollapsiblePane.module.scss' @@ -44,17 +44,7 @@ const CollapsiblePane: FC = (props: CollapsiblePaneProps) setIsOpen(!!props.isOpen) }, [props.isOpen]) - useEffect(() => { - const handleClickOutside: (ev: MouseEvent) => void = (ev: MouseEvent) => { - if (elRef.current && !elRef.current.contains(ev.target)) { - close() - } - } - if (isOpen) { - document.addEventListener('click', handleClickOutside) - } - return () => document.removeEventListener('click', handleClickOutside) - }, [close, isOpen]) + useClickOutside(elRef.current, close, isOpen) return (
    Date: Wed, 7 Sep 2022 14:36:36 -0700 Subject: [PATCH 027/113] TCA-336 #comment This commit forces a dev deployment to see if it's a permissions issue #time 10m --- cypress.config.ts | 1 + src-ts/tools/learn/learn-config/learn.bsouza.config.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cypress.config.ts b/cypress.config.ts index 97651723a..fc55f43d8 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -23,6 +23,7 @@ export default defineConfig({ video: true, }) +// adds the config to node setup events function setUpNodeEvents( on: Cypress.PluginEvents, config: Cypress.PluginConfigOptions diff --git a/src-ts/tools/learn/learn-config/learn.bsouza.config.ts b/src-ts/tools/learn/learn-config/learn.bsouza.config.ts index 58b0b8dd1..ab591ccaa 100644 --- a/src-ts/tools/learn/learn-config/learn.bsouza.config.ts +++ b/src-ts/tools/learn/learn-config/learn.bsouza.config.ts @@ -5,5 +5,5 @@ import { LearnConfigDev } from './learn.dev.config' export const LearnConfigBsouza: LearnConfigModel = { ...LearnConfigDev, // API: LearnConfigDefault.API, - // CLIENT: LearnConfigDefault.CLIENT, + CLIENT: LearnConfigDefault.CLIENT, } From 1e2fdcc4ecc3778ad2fcd534716c7a2a694bd82e Mon Sep 17 00:00:00 2001 From: Brooke Date: Thu, 8 Sep 2022 09:13:24 -0700 Subject: [PATCH 028/113] TCA-400 #comment This commit temporarily skips the cypress test bc the test needs to be debugged #time 5m --- cypress/e2e/home/home.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/home/home.spec.ts b/cypress/e2e/home/home.spec.ts index 43c584180..a1b877a9b 100644 --- a/cypress/e2e/home/home.spec.ts +++ b/cypress/e2e/home/home.spec.ts @@ -2,7 +2,8 @@ describe('Landing Page', () => { beforeEach(() => cy.visit('/')) - it('loads landing page should be successfully', () => { + // TCA-336 temporarily skip this bc the site isn't loading + it.skip('loads landing page should be successfully', () => { cy.get('[data-id="root"]').should('be.visible') }) From 9823c1a61473bd5eb19ea55beb76b797181bc2f3 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Fri, 9 Sep 2022 10:39:21 +0800 Subject: [PATCH 029/113] refine e2e --- .circleci/config.yml | 42 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index ccbd83edc..cbab2e9be 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,13 +8,16 @@ deploy_defaults: &deploy_defaults docker: - image: cimg/python:3.10.2 +test_defaults: &test_defaults + docker: + - image: cypress/browsers:node16.14.2-slim-chrome100-ff99-edge + install_build_dependency: &install_build_dependency name: Installation of build and deployment dependencies. command: | apt update apt install jq -y apt install python3-pip -y - apt install libgtk2.0-0 libgtk-3-0 libgbm-dev libnotify-dev libgconf-2-4 libnss3 libxss1 libasound2 libxtst6 xauth xvfb -y pip3 install awscli --upgrade install_dependency: &install_dependency @@ -37,7 +40,6 @@ save_cache_settings: &save_cache_settings key: connect-node-modules-{{ checksum "yarn.lock" }} paths: - node_modules - - /root/.cache/Cypress running_yarn_tslint: &running_yarn_tslint name: Running Yarn tslint @@ -47,6 +49,13 @@ running_yarn_tslint: &running_yarn_tslint running_yarn_build: &running_yarn_build name: Running Yarn Build + command: | + source buildenvvar + yarn install + yarn build + +running_yarn_test: &running_yarn_test + name: Running Yarn Test Build command: | source buildenvvar yarn install @@ -83,6 +92,20 @@ build_steps: &build_steps - run: *install_deploysuite - run: *build_configuration_fetch - run: *running_yarn_build + - persist_to_workspace: *workspace_persist + +test_steps: &test_steps + # Initialization. + - checkout + - setup_remote_docker + - restore_cache: + key: test-node-modules-{{ checksum "yarn.lock" }} + - save_cache: + key: test-node-modules-{{ checksum "yarn.lock" }} + paths: + - node_modules + - /root/.cache/Cypress + - run: *running_yarn_test - store_test_results: path: cypress/test-report - store_artifacts: @@ -91,7 +114,6 @@ build_steps: &build_steps path: cypress/videos - store_artifacts: path: cypress/screenshots - - persist_to_workspace: *workspace_persist deploy_steps: &deploy_steps - checkout @@ -139,6 +161,14 @@ jobs: LOGICAL_ENV: "prod" APPNAME: "platform-ui-mvp" steps: *build_steps + + test-dev: + <<: *test_defaults + environment: + DEPLOY_ENV: "DEV" + LOGICAL_ENV: "dev" + APPNAME: "platform-ui-mvp" + steps: *test_steps # Just tests commited code. deployDev: @@ -208,3 +238,9 @@ workflows: branches: only: - master + + - test-dev: + filters: + branches: + ignore: + - develop \ No newline at end of file From d77db70c7d2d90820b65cbf5e6b1f5a0848f3303 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Fri, 9 Sep 2022 11:03:13 +0800 Subject: [PATCH 030/113] include env var --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index cbab2e9be..7eef21350 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -105,6 +105,8 @@ test_steps: &test_steps paths: - node_modules - /root/.cache/Cypress + - run: *install_deploysuite + - run: *build_configuration_fetch - run: *running_yarn_test - store_test_results: path: cypress/test-report From db39b001de72af7e8ebe6cb60fc971ea5c486b0c Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Fri, 9 Sep 2022 11:05:33 +0800 Subject: [PATCH 031/113] env prepare --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7eef21350..0c3b92bfd 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -105,6 +105,7 @@ test_steps: &test_steps paths: - node_modules - /root/.cache/Cypress + - run: *install_build_dependency - run: *install_deploysuite - run: *build_configuration_fetch - run: *running_yarn_test From 2175923bb47a51e89aabd7d8a66ff6ffea6b5574 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Fri, 9 Sep 2022 11:09:41 +0800 Subject: [PATCH 032/113] remove test env --- .circleci/config.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0c3b92bfd..1d4499df3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -57,7 +57,6 @@ running_yarn_build: &running_yarn_build running_yarn_test: &running_yarn_test name: Running Yarn Test Build command: | - source buildenvvar yarn install yarn cypress install yarn build @@ -105,9 +104,6 @@ test_steps: &test_steps paths: - node_modules - /root/.cache/Cypress - - run: *install_build_dependency - - run: *install_deploysuite - - run: *build_configuration_fetch - run: *running_yarn_test - store_test_results: path: cypress/test-report From 33eac904a50a97c0cde730e3a64eb4f719d84581 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Fri, 9 Sep 2022 15:34:47 +0800 Subject: [PATCH 033/113] merge dev --- cypress/e2e/home/home.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cypress/e2e/home/home.spec.ts b/cypress/e2e/home/home.spec.ts index a1b877a9b..09e9bfc94 100644 --- a/cypress/e2e/home/home.spec.ts +++ b/cypress/e2e/home/home.spec.ts @@ -3,11 +3,11 @@ describe('Landing Page', () => { beforeEach(() => cy.visit('/')) // TCA-336 temporarily skip this bc the site isn't loading - it.skip('loads landing page should be successfully', () => { + it('loads landing page should be successfully', () => { cy.get('[data-id="root"]').should('be.visible') }) - it.skip('loads landing page should fail', () => { + it('loads landing page should fail', () => { cy.get('[data-id="root"]').should('not.be.visible') }) }) From 6ecf7f3926b985092997755190cccf94fc15d716 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Fri, 9 Sep 2022 15:35:54 +0800 Subject: [PATCH 034/113] try fail and deploy --- .circleci/config.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1d4499df3..26afa921e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -228,6 +228,7 @@ workflows: branches: only: - dev + - circle-ci-setup-v7 - deployProd: context : org-global From 10ce989b1e4d7652e0cf8496dd5a1ebafc7e02df Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Fri, 9 Sep 2022 15:41:45 +0800 Subject: [PATCH 035/113] reset --- .circleci/config.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 26afa921e..1d4499df3 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -228,7 +228,6 @@ workflows: branches: only: - dev - - circle-ci-setup-v7 - deployProd: context : org-global From 83446a1a552b535fc35f5c2561dfabebe3b7ab36 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Fri, 9 Sep 2022 21:51:42 +0800 Subject: [PATCH 036/113] execute test --- .circleci/config.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1d4499df3..58f6e8ae9 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -239,7 +239,4 @@ workflows: - master - test-dev: - filters: - branches: - ignore: - - develop \ No newline at end of file + context : org-global \ No newline at end of file From f6ce5cba2c316ca991a66d997a2bc00bbac19d11 Mon Sep 17 00:00:00 2001 From: LieutenantRoger Date: Fri, 9 Sep 2022 22:16:47 +0800 Subject: [PATCH 037/113] switch the command seqneuce --- .circleci/config.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 58f6e8ae9..1c16fe79b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -99,12 +99,12 @@ test_steps: &test_steps - setup_remote_docker - restore_cache: key: test-node-modules-{{ checksum "yarn.lock" }} - - save_cache: - key: test-node-modules-{{ checksum "yarn.lock" }} - paths: - - node_modules - - /root/.cache/Cypress - run: *running_yarn_test + - save_cache: + key: test-node-modules-{{ checksum "yarn.lock" }} + paths: + - node_modules + - /root/.cache/Cypress - store_test_results: path: cypress/test-report - store_artifacts: From efe4e018107a70e7c665a68bdf15224d23e027d7 Mon Sep 17 00:00:00 2001 From: Brooke Date: Fri, 9 Sep 2022 08:16:47 -0700 Subject: [PATCH 038/113] TCA-408 clean up for dev #time 10m --- cypress/e2e/home/home.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cypress/e2e/home/home.spec.ts b/cypress/e2e/home/home.spec.ts index 09e9bfc94..43c584180 100644 --- a/cypress/e2e/home/home.spec.ts +++ b/cypress/e2e/home/home.spec.ts @@ -2,12 +2,11 @@ describe('Landing Page', () => { beforeEach(() => cy.visit('/')) - // TCA-336 temporarily skip this bc the site isn't loading it('loads landing page should be successfully', () => { cy.get('[data-id="root"]').should('be.visible') }) - it('loads landing page should fail', () => { + it.skip('loads landing page should fail', () => { cy.get('[data-id="root"]').should('not.be.visible') }) }) From b3cb1642e8316f9e806e5987d7329d361a8bd04c Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Mon, 12 Sep 2022 22:07:40 +0300 Subject: [PATCH 039/113] Implements GAME-81 --- package.json | 1 + .../environment.default.config.ts | 3 + .../environments/environment.dev.config.ts | 3 + .../environments/environment.prod.config.ts | 3 + src-ts/lib/global-config.model.ts | 3 + src-ts/lib/svgs/Icon-column-sort.svg | 3 + src-ts/lib/svgs/index.ts | 4 +- .../gamification-admin/GamificationAdmin.tsx | 11 +- .../gamification-admin.routes.tsx | 2 +- .../pages/badge-detail/BadgeDetailPage.tsx | 2 + .../BadgeListingPage.module.scss | 110 ++++++++++++++++-- .../pages/badge-listing/BadgeListingPage.tsx | 60 +++++++++- yarn.lock | 5 + 13 files changed, 189 insertions(+), 21 deletions(-) create mode 100644 src-ts/lib/svgs/Icon-column-sort.svg diff --git a/package.json b/package.json index bf9628928..588b91442 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "redux-thunk": "^2.4.1", "sass": "^1.49.8", "styled-components": "^5.3.5", + "swr": "^1.3.0", "tc-auth-lib": "topcoder-platform/tc-auth-lib#1.0.4", "typescript": "^4.6.3", "uuid": "^8.3.2" diff --git a/src-ts/config/environments/environment.default.config.ts b/src-ts/config/environments/environment.default.config.ts index 8df336dc1..0195d6daa 100644 --- a/src-ts/config/environments/environment.default.config.ts +++ b/src-ts/config/environments/environment.default.config.ts @@ -14,6 +14,9 @@ export const EnvironmentConfigDefault: EnvironmentConfigModel = { V5: 'https://api.topcoder-dev.com/v5', }, ENV: 'default', + GAMIFICATION: { + ORG_ID: '6052dd9b-ea80-494b-b258-edd1331e27a3', + }, LOGGING: { PUBLIC_TOKEN: 'puba0825671e469d16f940c5a30dc738f11', SERVICE: 'platform-ui', diff --git a/src-ts/config/environments/environment.dev.config.ts b/src-ts/config/environments/environment.dev.config.ts index 61c8ec1e7..be58c0381 100644 --- a/src-ts/config/environments/environment.dev.config.ts +++ b/src-ts/config/environments/environment.dev.config.ts @@ -9,6 +9,9 @@ export const EnvironmentConfigDev: EnvironmentConfigModel = { }, DISABLED_TOOLS: [], ENV: 'dev', + GAMIFICATION: { + ORG_ID: '6052dd9b-ea80-494b-b258-edd1331e27a3', + }, // TODO: Move stripe creds to .env file STRIPE: { ADMIN_TOKEN: diff --git a/src-ts/config/environments/environment.prod.config.ts b/src-ts/config/environments/environment.prod.config.ts index 4e4bd4e6b..a13f50dff 100644 --- a/src-ts/config/environments/environment.prod.config.ts +++ b/src-ts/config/environments/environment.prod.config.ts @@ -17,6 +17,9 @@ export const EnvironmentConfigProd: EnvironmentConfigModel = { }, DISABLED_TOOLS: [], ENV: 'prod', + GAMIFICATION: { + ORG_ID: 'e111f8df-6ac8-44d1-b4da-bb916f5e3425', + }, // TODO: Move stripe creds to .env file STRIPE: { ADMIN_TOKEN: diff --git a/src-ts/lib/global-config.model.ts b/src-ts/lib/global-config.model.ts index ddf9b8ce4..10fde8e9c 100644 --- a/src-ts/lib/global-config.model.ts +++ b/src-ts/lib/global-config.model.ts @@ -11,6 +11,9 @@ export interface GlobalConfig { } DISABLED_TOOLS?: Array ENV: string + GAMIFICATION: { + ORG_ID: string + }, LOGGING: { PUBLIC_TOKEN: string SERVICE: string diff --git a/src-ts/lib/svgs/Icon-column-sort.svg b/src-ts/lib/svgs/Icon-column-sort.svg new file mode 100644 index 000000000..ab0226b61 --- /dev/null +++ b/src-ts/lib/svgs/Icon-column-sort.svg @@ -0,0 +1,3 @@ + + + diff --git a/src-ts/lib/svgs/index.ts b/src-ts/lib/svgs/index.ts index 55c230790..2bd3eda58 100644 --- a/src-ts/lib/svgs/index.ts +++ b/src-ts/lib/svgs/index.ts @@ -7,6 +7,7 @@ import { ReactComponent as GithubIcon } from './github.svg' import { ReactComponent as GitlabIcon } from './gitlab.svg' import { ReactComponent as ArrowIcon } from './icon-arrow.svg' import { ReactComponent as BackArrowIcon } from './icon-back-arrow.svg' +import { ReactComponent as ColumnSortIcon } from './Icon-column-sort.svg' import { ReactComponent as LogoIcon } from './logo.svg' import { ReactComponent as SocialIconFacebook } from './social-fb-icon.svg' import { ReactComponent as SocialIconInstagram } from './social-insta-icon.svg' @@ -35,5 +36,6 @@ export { SocialShareLinkedIn, TooltipArrowIcon, GitlabIcon, - GithubIcon + GithubIcon, + ColumnSortIcon } diff --git a/src-ts/tools/gamification-admin/GamificationAdmin.tsx b/src-ts/tools/gamification-admin/GamificationAdmin.tsx index 860b2c3ae..ec6fd9be2 100644 --- a/src-ts/tools/gamification-admin/GamificationAdmin.tsx +++ b/src-ts/tools/gamification-admin/GamificationAdmin.tsx @@ -1,10 +1,12 @@ import { FC, useContext } from 'react' import { Outlet, Routes } from 'react-router-dom' +import { SWRConfig } from 'swr' import { routeContext, RouteContextData, } from '../../lib' +import { getAsync } from '../../lib/functions/xhr-functions/xhr.functions' export const toolTitle: string = 'Gamification Admin' @@ -13,12 +15,17 @@ const GamificationAdmin: FC<{}> = () => { const { getChildRoutes }: RouteContextData = useContext(routeContext) return ( - <> + getAsync(resource), + refreshInterval: 60000, // 1 min + }} + > {getChildRoutes(toolTitle)} - + ) } diff --git a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx index 5b8a60acd..24ee0b8c1 100644 --- a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx +++ b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx @@ -22,7 +22,7 @@ export const gamificationAdminRoutes: Array = [ }, { element: , - route: '/badge-detail', + route: '/badge-detail/:id', }, ], element: , diff --git a/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx b/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx index 4923e6c95..a609b4ecb 100644 --- a/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx @@ -1,4 +1,5 @@ import { FC, useMemo } from 'react' +// import { useParams } from 'react-router-dom' import { Breadcrumb, BreadcrumbItemModel, ContentLayout } from '../../../../lib' import { baseUrl } from '../../gamification-admin.routes' @@ -7,6 +8,7 @@ import { toolTitle } from '../../GamificationAdmin' import styles from './BadgeDetailPage.module.scss' const BadgeDetailPage: FC = () => { + // const { id: badgeID } : { badgeID: string } = useParams() const breadcrumb: Array = useMemo(() => [ { name: toolTitle, url: baseUrl }, { name: 'badge detail', url: '#' }, diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss index 7da13b030..175072a97 100644 --- a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss +++ b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss @@ -1,17 +1,105 @@ -.contentLayout { - width: 100%; - padding-bottom: 0; +@import "../../../../lib/styles/includes"; - .contentLayout-outer { - width: 100%; +.container { + display: flex; + flex-direction: column; + padding-top: 32px; + + .badges-table-header { + display: flex; + justify-content: space-between; + padding-bottom: 17px; + color: #767676; + @include font-barlow; + @include font-weight-semibold; + font-size: 11px; + line-height: 11px; + + .col-sort { + display: flex; + margin-left: 94px; + + svg { + margin-left: 6px; + cursor: pointer; + } + } + + div:last-child { + margin-right: 100px; - .contentLayout-inner { - width: 100%; - overflow: visible; + @media (max-width: 768px) { + display: none; + } } } -} -.container { - display: flex; + .badges-table { + display: flex; + flex-direction: column; + + .badge-row { + display: flex; + justify-content: space-between; + padding: 16px 24px; + + @media (max-width: 768px) { + flex-direction: column; + } + + &:nth-child(odd) { + background-color: #F4F4F4; + border-radius: 8px; + } + + .badge { + display: flex; + align-items: center; + + .badge-image { + width: 48px; + height: 48px; + margin-right: 20px; + } + + .badge-image-disabled { + width: 48px; + height: 48px; + margin-right: 20px; + opacity: 0.5; + filter: grayscale(1); + } + + .badge-name { + font-size: 16px; + font-weight: 700; + line-height: 24px; + color: #2a2a2a; + } + } + + .actions { + display: flex; + align-items: center; + + @media (max-width: 768px) { + margin: 8px 0; + } + + .action-btn { + margin-right: 8px; + + &:last-child { + margin-right: 0; + } + } + } + } + } + + .loadbtn-wrap { + display: flex; + justify-content: center; + flex: 1; + } } diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx index 04fca91ee..4f30e3c2e 100644 --- a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx @@ -1,20 +1,68 @@ -import { FC } from 'react' +import { FC, useState } from 'react' +import { NavigateFunction, useNavigate } from 'react-router-dom' +// tslint:disable-next-line +import useSWRInfinite from 'swr/infinite' -import { ContentLayout } from '../../../../lib' +import { EnvironmentConfig } from '../../../../config' +import { Button, ButtonProps, ColumnSortIcon, ContentLayout, LoadingSpinner } from '../../../../lib' +import { baseUrl } from '../../gamification-admin.routes' import styles from './BadgeListingPage.module.scss' const BadgeListingPage: FC = () => { + const [order, setOrder]: any = useState({ by: 'badge_name', type: 'asc' }) + const navigate: NavigateFunction = useNavigate() + const getKey: any = (pageIndex: any, previousPageData: any) => { + if (previousPageData && !previousPageData.rows.length) { return undefined } // reached the end + return `${EnvironmentConfig.API.V5}/gamification/badges?organization_id=${EnvironmentConfig.GAMIFICATION.ORG_ID}&limit=12&offset=${pageIndex * 12}&order_by=${order.by}&order_type=${order.type}` + } + const { data: badges, size, setSize }: any = useSWRInfinite(getKey, { revalidateFirstPage: false }) + const loadedCnt: any = badges?.reduce((ps: any, a: any) => ps + a.rows.length, 0) + + const buttonConfig: ButtonProps = { + label: 'Create New Badge', + onClick: () => navigate(`${baseUrl}/create-badge`), + } + + if (!badges) { return } return (
    - +
    +
    BADGE NAME { + setOrder({ + by: order.by, + type: order.type === 'asc' ? 'desc' : 'asc', + }) + }} />
    +
    ACTIONS
    +
    +
    + { + badges.map((page: any) => page.rows.map((badge: any) =>
    +
    + {badge.badge_name} +

    {badge.badge_name}

    +
    +
    +
    +
    + )) + } +
    + { + badges[0].count !== loadedCnt &&
    +
    + }
    ) diff --git a/yarn.lock b/yarn.lock index 301fcda10..734710196 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14537,6 +14537,11 @@ svgo@^2.7.0: picocolors "^1.0.0" stable "^0.1.8" +swr@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/swr/-/swr-1.3.0.tgz#c6531866a35b4db37b38b72c45a63171faf9f4e8" + integrity sha512-dkghQrOl2ORX9HYrMDtPa7LTVHJjCTeZoB1dqTbnnEDlSvN8JEKpYIYurDfvbQFUUS8Cg8PceFVZNkW0KNNYPw== + symbol-tree@^3.2.2, symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" From fe1153eeab0ab5a2b8461f3851b90f6a81a3f331 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Wed, 14 Sep 2022 10:19:35 +0300 Subject: [PATCH 040/113] code review updates --- .../environment.default.config.ts | 3 --- .../environments/environment.dev.config.ts | 3 --- .../environments/environment.prod.config.ts | 3 --- .../gamification-admin/GamificationAdmin.tsx | 4 +-- .../config/gamification-config.model.ts | 3 +++ .../config/gamification.config.ts | 27 +++++++++++++++++++ .../config/gamification.default.config.ts | 5 ++++ .../config/gamification.dev.config.ts | 5 ++++ .../config/gamification.prod.config.ts | 5 ++++ .../tools/gamification-admin/config/index.ts | 1 + .../lib/hooks/getDataSource.ts | 5 ++++ .../pages/badge-detail/BadgeDetailPage.tsx | 1 - .../BadgeListingPage.module.scss | 2 +- .../pages/badge-listing/BadgeListingPage.tsx | 6 +++-- 14 files changed, 58 insertions(+), 15 deletions(-) create mode 100644 src-ts/tools/gamification-admin/config/gamification-config.model.ts create mode 100644 src-ts/tools/gamification-admin/config/gamification.config.ts create mode 100644 src-ts/tools/gamification-admin/config/gamification.default.config.ts create mode 100644 src-ts/tools/gamification-admin/config/gamification.dev.config.ts create mode 100644 src-ts/tools/gamification-admin/config/gamification.prod.config.ts create mode 100644 src-ts/tools/gamification-admin/config/index.ts create mode 100644 src-ts/tools/gamification-admin/lib/hooks/getDataSource.ts diff --git a/src-ts/config/environments/environment.default.config.ts b/src-ts/config/environments/environment.default.config.ts index 0195d6daa..8df336dc1 100644 --- a/src-ts/config/environments/environment.default.config.ts +++ b/src-ts/config/environments/environment.default.config.ts @@ -14,9 +14,6 @@ export const EnvironmentConfigDefault: EnvironmentConfigModel = { V5: 'https://api.topcoder-dev.com/v5', }, ENV: 'default', - GAMIFICATION: { - ORG_ID: '6052dd9b-ea80-494b-b258-edd1331e27a3', - }, LOGGING: { PUBLIC_TOKEN: 'puba0825671e469d16f940c5a30dc738f11', SERVICE: 'platform-ui', diff --git a/src-ts/config/environments/environment.dev.config.ts b/src-ts/config/environments/environment.dev.config.ts index be58c0381..61c8ec1e7 100644 --- a/src-ts/config/environments/environment.dev.config.ts +++ b/src-ts/config/environments/environment.dev.config.ts @@ -9,9 +9,6 @@ export const EnvironmentConfigDev: EnvironmentConfigModel = { }, DISABLED_TOOLS: [], ENV: 'dev', - GAMIFICATION: { - ORG_ID: '6052dd9b-ea80-494b-b258-edd1331e27a3', - }, // TODO: Move stripe creds to .env file STRIPE: { ADMIN_TOKEN: diff --git a/src-ts/config/environments/environment.prod.config.ts b/src-ts/config/environments/environment.prod.config.ts index a13f50dff..4e4bd4e6b 100644 --- a/src-ts/config/environments/environment.prod.config.ts +++ b/src-ts/config/environments/environment.prod.config.ts @@ -17,9 +17,6 @@ export const EnvironmentConfigProd: EnvironmentConfigModel = { }, DISABLED_TOOLS: [], ENV: 'prod', - GAMIFICATION: { - ORG_ID: 'e111f8df-6ac8-44d1-b4da-bb916f5e3425', - }, // TODO: Move stripe creds to .env file STRIPE: { ADMIN_TOKEN: diff --git a/src-ts/tools/gamification-admin/GamificationAdmin.tsx b/src-ts/tools/gamification-admin/GamificationAdmin.tsx index ec6fd9be2..3d85c3598 100644 --- a/src-ts/tools/gamification-admin/GamificationAdmin.tsx +++ b/src-ts/tools/gamification-admin/GamificationAdmin.tsx @@ -5,8 +5,8 @@ import { SWRConfig } from 'swr' import { routeContext, RouteContextData, + xhrGetAsync, } from '../../lib' -import { getAsync } from '../../lib/functions/xhr-functions/xhr.functions' export const toolTitle: string = 'Gamification Admin' @@ -17,7 +17,7 @@ const GamificationAdmin: FC<{}> = () => { return ( getAsync(resource), + fetcher: (resource) => xhrGetAsync(resource), refreshInterval: 60000, // 1 min }} > diff --git a/src-ts/tools/gamification-admin/config/gamification-config.model.ts b/src-ts/tools/gamification-admin/config/gamification-config.model.ts new file mode 100644 index 000000000..f090f1389 --- /dev/null +++ b/src-ts/tools/gamification-admin/config/gamification-config.model.ts @@ -0,0 +1,3 @@ +export interface GamificationConfigModel { + ORG_ID: string +} diff --git a/src-ts/tools/gamification-admin/config/gamification.config.ts b/src-ts/tools/gamification-admin/config/gamification.config.ts new file mode 100644 index 000000000..e204a4040 --- /dev/null +++ b/src-ts/tools/gamification-admin/config/gamification.config.ts @@ -0,0 +1,27 @@ +import { EnvironmentConfig } from '../../../config' + +import { GamificationConfigModel } from './gamification-config.model' +import { GamificationConfigDefault } from './gamification.default.config' +import { GamificationConfigDev } from './gamification.dev.config' +import { GamificationConfigProd } from './gamification.prod.config' + +function getConfig(): GamificationConfigModel { + + switch (EnvironmentConfig.ENV) { + + case 'dev': + return GamificationConfigDev + + case 'prod': + return GamificationConfigProd + + default: + return GamificationConfigDefault + } +} + +const GamificationConfig: GamificationConfigModel = { + ...getConfig(), +} + +export default GamificationConfig diff --git a/src-ts/tools/gamification-admin/config/gamification.default.config.ts b/src-ts/tools/gamification-admin/config/gamification.default.config.ts new file mode 100644 index 000000000..aa7ad4626 --- /dev/null +++ b/src-ts/tools/gamification-admin/config/gamification.default.config.ts @@ -0,0 +1,5 @@ +import { GamificationConfigModel } from './gamification-config.model' + +export const GamificationConfigDefault: GamificationConfigModel = { + ORG_ID: '6052dd9b-ea80-494b-b258-edd1331e27a3', +} diff --git a/src-ts/tools/gamification-admin/config/gamification.dev.config.ts b/src-ts/tools/gamification-admin/config/gamification.dev.config.ts new file mode 100644 index 000000000..63bd27ddf --- /dev/null +++ b/src-ts/tools/gamification-admin/config/gamification.dev.config.ts @@ -0,0 +1,5 @@ +import { GamificationConfigModel } from './gamification-config.model' + +export const GamificationConfigDev: GamificationConfigModel = { + ORG_ID: '6052dd9b-ea80-494b-b258-edd1331e27a3', +} diff --git a/src-ts/tools/gamification-admin/config/gamification.prod.config.ts b/src-ts/tools/gamification-admin/config/gamification.prod.config.ts new file mode 100644 index 000000000..922edc652 --- /dev/null +++ b/src-ts/tools/gamification-admin/config/gamification.prod.config.ts @@ -0,0 +1,5 @@ +import { GamificationConfigModel } from './gamification-config.model' + +export const GamificationConfigProd: GamificationConfigModel = { + ORG_ID: 'e111f8df-6ac8-44d1-b4da-bb916f5e3425', +} diff --git a/src-ts/tools/gamification-admin/config/index.ts b/src-ts/tools/gamification-admin/config/index.ts new file mode 100644 index 000000000..60ed70fc0 --- /dev/null +++ b/src-ts/tools/gamification-admin/config/index.ts @@ -0,0 +1 @@ +export { default as GamificationConfig } from './gamification.config' diff --git a/src-ts/tools/gamification-admin/lib/hooks/getDataSource.ts b/src-ts/tools/gamification-admin/lib/hooks/getDataSource.ts new file mode 100644 index 000000000..d81ffdcab --- /dev/null +++ b/src-ts/tools/gamification-admin/lib/hooks/getDataSource.ts @@ -0,0 +1,5 @@ +import { EnvironmentConfig } from '../../../../config' + +export default function getDataSource(): string { + return `${EnvironmentConfig.API.V5}/gamification` +} diff --git a/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx b/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx index a609b4ecb..c58a04227 100644 --- a/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx @@ -1,5 +1,4 @@ import { FC, useMemo } from 'react' -// import { useParams } from 'react-router-dom' import { Breadcrumb, BreadcrumbItemModel, ContentLayout } from '../../../../lib' import { baseUrl } from '../../gamification-admin.routes' diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss index 175072a97..2be19a603 100644 --- a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss +++ b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss @@ -28,7 +28,7 @@ div:last-child { margin-right: 100px; - @media (max-width: 768px) { + @include ltemd { display: none; } } diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx index 4f30e3c2e..053b17b28 100644 --- a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx @@ -3,18 +3,20 @@ import { NavigateFunction, useNavigate } from 'react-router-dom' // tslint:disable-next-line import useSWRInfinite from 'swr/infinite' -import { EnvironmentConfig } from '../../../../config' import { Button, ButtonProps, ColumnSortIcon, ContentLayout, LoadingSpinner } from '../../../../lib' +import { GamificationConfig } from '../../config' import { baseUrl } from '../../gamification-admin.routes' +import getDataSource from '../../lib/hooks/getDataSource' import styles from './BadgeListingPage.module.scss' const BadgeListingPage: FC = () => { const [order, setOrder]: any = useState({ by: 'badge_name', type: 'asc' }) const navigate: NavigateFunction = useNavigate() + const dataSource: string = getDataSource() const getKey: any = (pageIndex: any, previousPageData: any) => { if (previousPageData && !previousPageData.rows.length) { return undefined } // reached the end - return `${EnvironmentConfig.API.V5}/gamification/badges?organization_id=${EnvironmentConfig.GAMIFICATION.ORG_ID}&limit=12&offset=${pageIndex * 12}&order_by=${order.by}&order_type=${order.type}` + return `${dataSource}/badges?organization_id=${GamificationConfig.ORG_ID}&limit=12&offset=${pageIndex * 12}&order_by=${order.by}&order_type=${order.type}` } const { data: badges, size, setSize }: any = useSWRInfinite(getKey, { revalidateFirstPage: false }) const loadedCnt: any = badges?.reduce((ps: any, a: any) => ps + a.rows.length, 0) From 126806e3e3f414d421399a121b2d04a18311fe3a Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Wed, 14 Sep 2022 12:00:26 +0300 Subject: [PATCH 041/113] switch to heroicon for sort column trigger --- src-ts/lib/svgs/Icon-column-sort.svg | 3 -- src-ts/lib/svgs/index.ts | 4 +-- .../BadgeListingPage.module.scss | 7 +++-- .../pages/badge-listing/BadgeListingPage.tsx | 28 +++++++++++++------ 4 files changed, 24 insertions(+), 18 deletions(-) delete mode 100644 src-ts/lib/svgs/Icon-column-sort.svg diff --git a/src-ts/lib/svgs/Icon-column-sort.svg b/src-ts/lib/svgs/Icon-column-sort.svg deleted file mode 100644 index ab0226b61..000000000 --- a/src-ts/lib/svgs/Icon-column-sort.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src-ts/lib/svgs/index.ts b/src-ts/lib/svgs/index.ts index 2bd3eda58..55c230790 100644 --- a/src-ts/lib/svgs/index.ts +++ b/src-ts/lib/svgs/index.ts @@ -7,7 +7,6 @@ import { ReactComponent as GithubIcon } from './github.svg' import { ReactComponent as GitlabIcon } from './gitlab.svg' import { ReactComponent as ArrowIcon } from './icon-arrow.svg' import { ReactComponent as BackArrowIcon } from './icon-back-arrow.svg' -import { ReactComponent as ColumnSortIcon } from './Icon-column-sort.svg' import { ReactComponent as LogoIcon } from './logo.svg' import { ReactComponent as SocialIconFacebook } from './social-fb-icon.svg' import { ReactComponent as SocialIconInstagram } from './social-insta-icon.svg' @@ -36,6 +35,5 @@ export { SocialShareLinkedIn, TooltipArrowIcon, GitlabIcon, - GithubIcon, - ColumnSortIcon + GithubIcon } diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss index 2be19a603..5d566a4fc 100644 --- a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss +++ b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss @@ -1,4 +1,5 @@ @import "../../../../lib/styles/includes"; +@import "../../../../lib/styles/variables"; .container { display: flex; @@ -9,7 +10,7 @@ display: flex; justify-content: space-between; padding-bottom: 17px; - color: #767676; + color: $black-60; @include font-barlow; @include font-weight-semibold; font-size: 11px; @@ -18,10 +19,10 @@ .col-sort { display: flex; margin-left: 94px; + align-items: center; svg { - margin-left: 6px; - cursor: pointer; + color: $black-100; } } diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx index 053b17b28..ab8013099 100644 --- a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx @@ -1,9 +1,9 @@ import { FC, useState } from 'react' import { NavigateFunction, useNavigate } from 'react-router-dom' // tslint:disable-next-line -import useSWRInfinite from 'swr/infinite' +import useSWRInfinite from 'swr/infinite' -import { Button, ButtonProps, ColumnSortIcon, ContentLayout, LoadingSpinner } from '../../../../lib' +import { Button, ButtonProps, ContentLayout, IconOutline, LoadingSpinner } from '../../../../lib' import { GamificationConfig } from '../../config' import { baseUrl } from '../../gamification-admin.routes' import getDataSource from '../../lib/hooks/getDataSource' @@ -20,6 +20,12 @@ const BadgeListingPage: FC = () => { } const { data: badges, size, setSize }: any = useSWRInfinite(getKey, { revalidateFirstPage: false }) const loadedCnt: any = badges?.reduce((ps: any, a: any) => ps + a.rows.length, 0) + const onOrderClick: any = () => { + setOrder({ + by: order.by, + type: order.type === 'asc' ? 'desc' : 'asc', + }) + } const buttonConfig: ButtonProps = { label: 'Create New Badge', @@ -35,12 +41,16 @@ const BadgeListingPage: FC = () => { >
    -
    BADGE NAME { - setOrder({ - by: order.by, - type: order.type === 'asc' ? 'desc' : 'asc', - }) - }} />
    +
    + BADGE NAME + { + order.type === 'asc' ? ( +
    ACTIONS
    @@ -51,7 +61,7 @@ const BadgeListingPage: FC = () => {

    {badge.badge_name}

    -
    From c3ae18ad5b9b5b148e28ea51b699729016a9fc5d Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Wed, 14 Sep 2022 12:10:20 +0300 Subject: [PATCH 042/113] use _spacing whenever possible --- .../BadgeListingPage.module.scss | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss index 5d566a4fc..36db3452d 100644 --- a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss +++ b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.module.scss @@ -4,12 +4,12 @@ .container { display: flex; flex-direction: column; - padding-top: 32px; + padding-top: $space-xxxxl; .badges-table-header { display: flex; justify-content: space-between; - padding-bottom: 17px; + padding-bottom: $space-lg; color: $black-60; @include font-barlow; @include font-weight-semibold; @@ -42,14 +42,14 @@ .badge-row { display: flex; justify-content: space-between; - padding: 16px 24px; + padding: $space-lg $space-xxl; - @media (max-width: 768px) { + @include ltemd { flex-direction: column; } &:nth-child(odd) { - background-color: #F4F4F4; + background-color: $black-5; border-radius: 8px; } @@ -60,13 +60,13 @@ .badge-image { width: 48px; height: 48px; - margin-right: 20px; + margin-right: $space-xl; } .badge-image-disabled { width: 48px; height: 48px; - margin-right: 20px; + margin-right: $space-xl; opacity: 0.5; filter: grayscale(1); } @@ -75,7 +75,7 @@ font-size: 16px; font-weight: 700; line-height: 24px; - color: #2a2a2a; + color: $black-100; } } @@ -83,12 +83,12 @@ display: flex; align-items: center; - @media (max-width: 768px) { - margin: 8px 0; + @include ltemd { + margin: $space-sm 0; } .action-btn { - margin-right: 8px; + margin-right: $space-sm; &:last-child { margin-right: 0; From 5847caca1091485c66127fdbbdd592d54f4a5db1 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 15 Sep 2022 15:48:12 +0300 Subject: [PATCH 043/113] TCA-440: call certification completion based on assessment completion --- src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx | 2 +- .../tools/learn/learn-lib/course-outline/CourseOutline.tsx | 1 + .../course-outline/collapsible-item/CollapsibleItem.tsx | 5 ++--- .../learn-lib/lesson-provider/learn-module-meta.model.ts | 2 +- .../learn-user-certification-progress.model.ts | 2 ++ 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx b/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx index 9f123afc7..43b519f83 100644 --- a/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx +++ b/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx @@ -259,7 +259,7 @@ const FreeCodeCamp: FC<{}> = () => { useEffect(() => { if ( certificateProgress && - certificateProgress.courseProgressPercentage === 100 && + certificateProgress.certificationProgressPercentage === 100 && certificateProgress.status === UserCertificationProgressStatus.inProgress ) { userCertificationProgressUpdateAsync( diff --git a/src-ts/tools/learn/learn-lib/course-outline/CourseOutline.tsx b/src-ts/tools/learn/learn-lib/course-outline/CourseOutline.tsx index e381863d1..65dd085cc 100644 --- a/src-ts/tools/learn/learn-lib/course-outline/CourseOutline.tsx +++ b/src-ts/tools/learn/learn-lib/course-outline/CourseOutline.tsx @@ -54,6 +54,7 @@ const CourseOutline: FC = (props: CourseOutlineProps) => { shortDescription={module.meta.introCopy} title={module.meta.name} onItemClick={props.onItemNavigate} + isAssessment={module.meta.isAssessment} /> ))}
    diff --git a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx index cac0bd7fc..a58f53646 100644 --- a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx +++ b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx @@ -19,6 +19,7 @@ interface CollapsibleItemProps { duration: LearnModule['meta']['estimatedCompletionTime'] itemId?: (item: any) => string items: Array + isAssessment: boolean lessonsCount: number moduleKey: string onItemClick: (item: any) => void @@ -31,8 +32,6 @@ interface CollapsibleItemProps { const CollapsibleItem: FC = (props: CollapsibleItemProps) => { const [isOpen, setIsOpen]: [boolean, Dispatch>] = useState(false) - const isAssessment: boolean = props.lessonsCount === 1 - const toggle: () => void = useCallback(() => { setIsOpen(open => !open) }, []) @@ -89,7 +88,7 @@ const CollapsibleItem: FC = (props: CollapsibleItemProps)
    - {isAssessment && ( + {props.isAssessment && (
    assessment
    diff --git a/src-ts/tools/learn/learn-lib/lesson-provider/learn-module-meta.model.ts b/src-ts/tools/learn/learn-lib/lesson-provider/learn-module-meta.model.ts index 1c8d08b1f..bb2a59385 100644 --- a/src-ts/tools/learn/learn-lib/lesson-provider/learn-module-meta.model.ts +++ b/src-ts/tools/learn/learn-lib/lesson-provider/learn-module-meta.model.ts @@ -5,6 +5,6 @@ export interface LearnModuleMeta { value: number } introCopy: Array - lessonCount: number + isAssessment: boolean name: string } diff --git a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/learn-user-certification-progress.model.ts b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/learn-user-certification-progress.model.ts index 88100541b..e08ac81f5 100644 --- a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/learn-user-certification-progress.model.ts +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/learn-user-certification-progress.model.ts @@ -7,6 +7,8 @@ export interface LearnUserCertificationProgress extends LearnModelBase { academicHonestyPolicyAcceptedAt?: number, certification: string certificationId: string + certificationProgressPercentage: number + certType: 'certification'; completedDate?: string courseId: string courseKey: string From 0ea5539d8de839dfb5c6566a58e1e2249b4e268e Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 15 Sep 2022 15:54:45 +0300 Subject: [PATCH 044/113] lint fix --- .../course-outline/collapsible-item/CollapsibleItem.tsx | 2 +- .../learn-user-certification-progress.model.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx index a58f53646..5380318c8 100644 --- a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx +++ b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx @@ -17,9 +17,9 @@ interface CollapsibleListItem { interface CollapsibleItemProps { active?: string duration: LearnModule['meta']['estimatedCompletionTime'] + isAssessment: boolean itemId?: (item: any) => string items: Array - isAssessment: boolean lessonsCount: number moduleKey: string onItemClick: (item: any) => void diff --git a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/learn-user-certification-progress.model.ts b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/learn-user-certification-progress.model.ts index e08ac81f5..2d9dc6f78 100644 --- a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/learn-user-certification-progress.model.ts +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/learn-user-certification-progress.model.ts @@ -8,7 +8,7 @@ export interface LearnUserCertificationProgress extends LearnModelBase { certification: string certificationId: string certificationProgressPercentage: number - certType: 'certification'; + certType: 'certification' completedDate?: string courseId: string courseKey: string From 51c92071f418055ff760ea1e3543095349c6e1ff Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 15 Sep 2022 18:10:52 +0300 Subject: [PATCH 045/113] TCA-444 - Learn Landing Header content --- .../learn-lib/wave-hero/WaveHero.module.scss | 8 +++---- .../learn/learn-lib/wave-hero/WaveHero.tsx | 2 +- .../learn/welcome/WelcomePage.module.scss | 15 ++++++++---- src-ts/tools/learn/welcome/WelcomePage.tsx | 8 ++++++- src-ts/tools/learn/welcome/tca-full-logo.svg | 24 +++++++++++++++++++ 5 files changed, 46 insertions(+), 11 deletions(-) create mode 100644 src-ts/tools/learn/welcome/tca-full-logo.svg diff --git a/src-ts/tools/learn/learn-lib/wave-hero/WaveHero.module.scss b/src-ts/tools/learn/learn-lib/wave-hero/WaveHero.module.scss index 996cb752a..7fb19c5aa 100755 --- a/src-ts/tools/learn/learn-lib/wave-hero/WaveHero.module.scss +++ b/src-ts/tools/learn/learn-lib/wave-hero/WaveHero.module.scss @@ -19,7 +19,7 @@ } } } - + &-inner { padding: calc($space-xxxxl + $space-sm) 0 $space-lg; @include contentWidth; @@ -47,13 +47,13 @@ flex-direction: column; } } - + &-card-col { flex: 0 0 auto; } - + &-text { @extend .body-medium-normal; - margin-top: $space-xxl; + margin-top: $space-sm; } } diff --git a/src-ts/tools/learn/learn-lib/wave-hero/WaveHero.tsx b/src-ts/tools/learn/learn-lib/wave-hero/WaveHero.tsx index a78e17b7b..907111c88 100755 --- a/src-ts/tools/learn/learn-lib/wave-hero/WaveHero.tsx +++ b/src-ts/tools/learn/learn-lib/wave-hero/WaveHero.tsx @@ -7,7 +7,7 @@ interface WaveHeroProps { children?: ReactNode text: string theme?: 'light' - title: string + title: ReactNode } const WaveHero: FC = (props: WaveHeroProps) => { diff --git a/src-ts/tools/learn/welcome/WelcomePage.module.scss b/src-ts/tools/learn/welcome/WelcomePage.module.scss index c00834ead..c44175908 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.module.scss +++ b/src-ts/tools/learn/welcome/WelcomePage.module.scss @@ -1,10 +1,15 @@ @import '../../../lib/styles/includes'; .hero-wrap { + svg:global(.tca-logo) { + margin-bottom: $space-xxl; + max-width: calc(100% - 70px); + } + :global(.hero-card-col) { width: 43.5%; max-width: 600px; - + @include ltemd { width: 100%; max-width: none; @@ -15,7 +20,7 @@ .courses-section { padding: $space-xxxxl 0; position: relative; - + @include ltemd { padding-top: $space-xxl; } @@ -38,12 +43,12 @@ @include ltelg { grid-template-columns: repeat(2, 1fr); } - + @media (max-width: 576px) { grid-template-columns: repeat(1, 1fr); } - + @include ltemd { margin-top: $space-xxl; } -} \ No newline at end of file +} diff --git a/src-ts/tools/learn/welcome/WelcomePage.tsx b/src-ts/tools/learn/welcome/WelcomePage.tsx index 12008470f..e437e9a43 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.tsx +++ b/src-ts/tools/learn/welcome/WelcomePage.tsx @@ -14,6 +14,7 @@ import { import { CoursesCard } from './courses-card' import { ProgressBlock } from './progress-block' import styles from './WelcomePage.module.scss' +import { ReactComponent as TcAcademyFullLogoSvg } from './tca-full-logo.svg' const WelcomePage: FC<{}> = () => { @@ -30,7 +31,12 @@ const WelcomePage: FC<{}> = () => {
    + + Welcome! + + )} text={` The Topcoder Academy will provide you with learning opportunities in the form of guided learning paths. diff --git a/src-ts/tools/learn/welcome/tca-full-logo.svg b/src-ts/tools/learn/welcome/tca-full-logo.svg new file mode 100644 index 000000000..9deee693b --- /dev/null +++ b/src-ts/tools/learn/welcome/tca-full-logo.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + From cfeb2d4c250a26e5b1fd194d5399864746fd55a0 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 15 Sep 2022 18:16:28 +0300 Subject: [PATCH 046/113] lint fixes --- src-ts/tools/learn/welcome/WelcomePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-ts/tools/learn/welcome/WelcomePage.tsx b/src-ts/tools/learn/welcome/WelcomePage.tsx index e437e9a43..303472002 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.tsx +++ b/src-ts/tools/learn/welcome/WelcomePage.tsx @@ -13,8 +13,8 @@ import { import { CoursesCard } from './courses-card' import { ProgressBlock } from './progress-block' -import styles from './WelcomePage.module.scss' import { ReactComponent as TcAcademyFullLogoSvg } from './tca-full-logo.svg' +import styles from './WelcomePage.module.scss' const WelcomePage: FC<{}> = () => { From f98d0de927102cc6fb31f6f0d23582114a143b5f Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 16 Sep 2022 11:12:04 +0300 Subject: [PATCH 047/113] init badge creation --- .../create-badge/CreateBadgePage.module.scss | 14 ---- .../pages/create-badge/CreateBadgePage.tsx | 22 +++++-- .../CreateBadgeForm.module.scss | 2 + .../create-badge-form/CreateBadgeForm.tsx | 49 ++++++++++++++ .../create-badge-form.config.tsx | 66 +++++++++++++++++++ .../create-badge-request.model.ts | 5 ++ .../create-badge-store/create-badge.store.ts | 9 +++ .../create-badge-store/index.ts | 2 + .../create-badge.functions.ts | 5 ++ .../create-badge-functions/index.ts | 2 + .../create-badge/create-badge-form/index.ts | 2 + 11 files changed, 158 insertions(+), 20 deletions(-) create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.module.scss create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/index.ts create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge.functions.ts create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/index.ts create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/index.ts diff --git a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss index 7da13b030..abde53a3a 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss +++ b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss @@ -1,17 +1,3 @@ -.contentLayout { - width: 100%; - padding-bottom: 0; - - .contentLayout-outer { - width: 100%; - - .contentLayout-inner { - width: 100%; - overflow: visible; - } - } -} - .container { display: flex; } diff --git a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx index 020b7f8eb..8756dc77f 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx @@ -1,8 +1,9 @@ -import { FC, useMemo } from 'react' +import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react' -import { Breadcrumb, BreadcrumbItemModel, ContentLayout } from '../../../../lib' +import { Breadcrumb, BreadcrumbItemModel, ContentLayout, FormDefinition, formOnReset, formGetInputFields } from '../../../../lib' import { baseUrl } from '../../gamification-admin.routes' import { toolTitle } from '../../GamificationAdmin' +import { CreateBadgeForm, createBadgeFormDef } from './create-badge-form' import styles from './CreateBadgePage.module.scss' @@ -12,16 +13,25 @@ const CreateBadgePage: FC = () => { { name: 'create badge', url: '#' }, ], []) + const [formDef, setFormDef]: [FormDefinition, Dispatch>] + = useState({ ...createBadgeFormDef }) + + function onSave(): void { + const updatedForm: FormDefinition = { ...formDef } + formOnReset(formGetInputFields(updatedForm.groups || [])) + setFormDef(updatedForm) + } + return (
    - +
    ) diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.module.scss b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.module.scss new file mode 100644 index 000000000..ca132e9b9 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.module.scss @@ -0,0 +1,2 @@ +@import '../../../../../lib/styles/includes'; + diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx new file mode 100644 index 000000000..5baf46a94 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx @@ -0,0 +1,49 @@ +import { FC, useContext } from 'react' + +import { Form, FormDefinition, formGetInputModel, FormInputModel, profileContext, ProfileContextData } from '../../../../../lib' + +import { CreateBadgeFormField } from './create-badge-form.config' +import { CreateBadgeRequest } from './create-badge-functions' +import { createBadgeSubmitRequestAsync } from './create-badge-functions/create-badge-store' +import styles from './CreateBadgeForm.module.scss' + +export interface CreateBadgeFormProps { + formDef: FormDefinition + onSave: () => void +} + +const CreateBadgeForm: FC = (props: CreateBadgeFormProps) => { + + const { profile }: ProfileContextData = useContext(profileContext) + + function generateRequest(inputs: ReadonlyArray): CreateBadgeRequest { + const badgeName: string = formGetInputModel(inputs, CreateBadgeFormField.badgeName).value as string + const badgeDesc: string = formGetInputModel(inputs, CreateBadgeFormField.badgeDesc).value as string + const badgeActive: string = formGetInputModel(inputs, CreateBadgeFormField.badgeActive).value as string + return { + badgeActive, + badgeName, + badgeDesc, + } + } + + async function saveAsync(request: CreateBadgeRequest): Promise { + return createBadgeSubmitRequestAsync(request) + .then(() => { + props.onSave() + }) + } + + return ( + <> +
    + + ) +} + +export default CreateBadgeForm diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx new file mode 100644 index 000000000..df79932da --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx @@ -0,0 +1,66 @@ +import { FormDefinition, validatorRequired, IconOutline, RadioButton } from '../../../../../lib' + +export enum CreateBadgeFormField { + badgeActive = 'badgeActive', + badgeName = 'badgeName', + badgeDesc = 'badgeDesc', +} + +export const createBadgeFormDef: FormDefinition = { + buttons: { + primaryGroup: [ + { + buttonStyle: 'primary', + isSubmit: true, + label: 'Save Badge', + size: 'lg', + type: 'submit', + }, + ], + secondaryGroup: [ + { + buttonStyle: 'icon-bordered', + size: 'lg', + icon: IconOutline.ChevronLeftIcon, + route: `/gamification-admin` + + }, + ] + }, + groups: [ + { + inputs: [ + { + label: 'Badge Name', + name: CreateBadgeFormField.badgeName, + type: 'text', + validators: [ + { + validator: validatorRequired, + }, + ], + }, + { + label: 'Badge Description', + name: CreateBadgeFormField.badgeDesc, + type: 'textarea', + validators: [ + { + validator: validatorRequired, + }, + ], + }, + { + label: 'Activate', + name: CreateBadgeFormField.badgeActive, + type: 'checkbox', + validators: [ + { + validator: validatorRequired, + }, + ], + } + ], + }, + ], +} diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts new file mode 100644 index 000000000..40e648857 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts @@ -0,0 +1,5 @@ +export interface CreateBadgeRequest { + badgeActive: string + badgeName: string + badgeDesc: string +} diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts new file mode 100644 index 000000000..0dea4b3ff --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts @@ -0,0 +1,9 @@ +import { EnvironmentConfig } from '../../../../../../../config' +import { xhrPostAsync } from '../../../../../../../lib' + +import { CreateBadgeRequest } from './create-badge-request.model' + +export async function submitRequestAsync(request: CreateBadgeRequest): Promise { + const url: string = `${EnvironmentConfig.API.V5}/gamification/badges` + await xhrPostAsync(url, request) +} diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/index.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/index.ts new file mode 100644 index 000000000..5f0511634 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/index.ts @@ -0,0 +1,2 @@ +export * from './create-badge-request.model' +export { submitRequestAsync as createBadgeSubmitRequestAsync } from './create-badge.store' diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge.functions.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge.functions.ts new file mode 100644 index 000000000..ddce9a486 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge.functions.ts @@ -0,0 +1,5 @@ +import { CreateBadgeRequest, createBadgeSubmitRequestAsync } from './create-badge-store' + +export async function submitRequestAsync(request: CreateBadgeRequest): Promise { + return createBadgeSubmitRequestAsync(request) +} diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/index.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/index.ts new file mode 100644 index 000000000..82368d47a --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/index.ts @@ -0,0 +1,2 @@ +export { type CreateBadgeRequest } from './create-badge-store' +export { submitRequestAsync as contactSupportSubmitRequestAsync } from './create-badge.functions' diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/index.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/index.ts new file mode 100644 index 000000000..a323b7903 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/index.ts @@ -0,0 +1,2 @@ +export { default as CreateBadgeForm } from './CreateBadgeForm' +export { createBadgeFormDef } from './create-badge-form.config' From cde8c845e9288f49a9608d9c79960aedc9554198 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Fri, 16 Sep 2022 15:50:24 +0300 Subject: [PATCH 048/113] TCA-440 - add slider on homepage with user's latest progress --- .../in-progress/InProgress.module.scss | 8 +- .../progress-block/ProgressBlock.module.scss | 6 +- .../cards-slider/CardsSlider.module.scss | 63 +++++++++++ .../cards-slider/CardsSlider.tsx | 49 +++++++++ .../progress-block/cards-slider/index.ts | 1 + .../progress-action/ProgressAction.tsx | 103 ++++++++---------- 6 files changed, 166 insertions(+), 64 deletions(-) create mode 100644 src-ts/tools/learn/welcome/progress-block/cards-slider/CardsSlider.module.scss create mode 100644 src-ts/tools/learn/welcome/progress-block/cards-slider/CardsSlider.tsx create mode 100644 src-ts/tools/learn/welcome/progress-block/cards-slider/index.ts diff --git a/src-ts/tools/learn/learn-lib/my-course-card/in-progress/InProgress.module.scss b/src-ts/tools/learn/learn-lib/my-course-card/in-progress/InProgress.module.scss index 1b525b6fd..152d7f6fb 100644 --- a/src-ts/tools/learn/learn-lib/my-course-card/in-progress/InProgress.module.scss +++ b/src-ts/tools/learn/learn-lib/my-course-card/in-progress/InProgress.module.scss @@ -8,10 +8,6 @@ display: flex; - &.large { - padding: $space-xxxxl; - } - @include ltelg { &, &.large { padding: $space-lg; @@ -119,8 +115,8 @@ .started-date { font-weight: bold; - + @include ltemd { order: -1; } -} \ No newline at end of file +} diff --git a/src-ts/tools/learn/welcome/progress-block/ProgressBlock.module.scss b/src-ts/tools/learn/welcome/progress-block/ProgressBlock.module.scss index 167346070..354f50713 100644 --- a/src-ts/tools/learn/welcome/progress-block/ProgressBlock.module.scss +++ b/src-ts/tools/learn/welcome/progress-block/ProgressBlock.module.scss @@ -4,14 +4,14 @@ .wrap { background: $black-5; border-radius: $space-sm; - padding: $space-xxl; + padding: $space-xxl $space-xxl $space-lg; color: $black-100; display: flex; flex-direction: column; - gap: $space-lg; + gap: $space-xxl; width: 100%; - + @include ltemd { padding: $space-lg; } diff --git a/src-ts/tools/learn/welcome/progress-block/cards-slider/CardsSlider.module.scss b/src-ts/tools/learn/welcome/progress-block/cards-slider/CardsSlider.module.scss new file mode 100644 index 000000000..f54fe4bcb --- /dev/null +++ b/src-ts/tools/learn/welcome/progress-block/cards-slider/CardsSlider.module.scss @@ -0,0 +1,63 @@ +@import '../../../../../lib/styles/includes'; + +.slides-wrap { + display: block; + position: relative; + z-index: 1; + overflow: hidden; +} + +.slide { + position: absolute; + top: 0; + left: 0; + width: 100%; + + opacity: 0; + visibility: hidden; + transition: 0.2s ease; + &:global(:not(.active)) { + pointer-events: none; + } + + + &:global(.is-prev) { + transform: translateX(-100%); + } + + &:global(.is-next) { + transform: translateX(100%); + } + + &:global(.active) { + position: relative; + opacity: 1; + transform: translateX(0); + visibility: visible; + } +} + +.nav-wrap { + display: flex; + align-items: center; + justify-content: center; + margin-top: $space-xxl; + + gap: calc($space-xs + $border-xs); +} + +.nav-dot { + display: block; + width: $space-md; + height: $space-md; + background: $black-40; + border-radius: 50%; + cursor: pointer; + + &:global(.active) { + width: calc($space-lg + $border); + height: calc($space-lg + $border); + border: $border solid $turq-100; + background: $tc-white; + } +} diff --git a/src-ts/tools/learn/welcome/progress-block/cards-slider/CardsSlider.tsx b/src-ts/tools/learn/welcome/progress-block/cards-slider/CardsSlider.tsx new file mode 100644 index 000000000..e41f99aab --- /dev/null +++ b/src-ts/tools/learn/welcome/progress-block/cards-slider/CardsSlider.tsx @@ -0,0 +1,49 @@ +import classNames from 'classnames' +import { fill } from 'lodash' +import { Children, Dispatch, FC, ReactNode, SetStateAction, useState } from 'react' + +import styles from './CardsSlider.module.scss' + +interface CardsSliderProps { + children: Array +} + +const CardsSlider: FC = (props: CardsSliderProps) => { + const [activeSlide, setActiveSlide]: [number, Dispatch>] = useState(2) + + const wrapSlides: (children: Array) => Array = (children: Array) => { + return Children.map(children, (child, index) => ( +
    index && 'is-prev', + activeSlide < index && 'is-next', + ) + } + > + {child} +
    + )) as Array + } + + return ( +
    +
    + {wrapSlides(props.children)} +
    +
    + {fill(Array(props.children.length), '').map((_, i) => ( + setActiveSlide(i)} + /> + ))} +
    +
    + ) +} + +export default CardsSlider diff --git a/src-ts/tools/learn/welcome/progress-block/cards-slider/index.ts b/src-ts/tools/learn/welcome/progress-block/cards-slider/index.ts new file mode 100644 index 000000000..f2113ef20 --- /dev/null +++ b/src-ts/tools/learn/welcome/progress-block/cards-slider/index.ts @@ -0,0 +1 @@ +export { default as CardsSlider } from './CardsSlider' diff --git a/src-ts/tools/learn/welcome/progress-block/progress-action/ProgressAction.tsx b/src-ts/tools/learn/welcome/progress-block/progress-action/ProgressAction.tsx index 3eb8997ec..9de579e3b 100644 --- a/src-ts/tools/learn/welcome/progress-block/progress-action/ProgressAction.tsx +++ b/src-ts/tools/learn/welcome/progress-block/progress-action/ProgressAction.tsx @@ -1,15 +1,18 @@ +import { orderBy } from 'lodash' import { FC, ReactNode, useMemo } from 'react' import { Button } from '../../../../../lib' import { LearnCertification, - LearningHat, + LearnUserCertificationProgress, MyCourseCompletedCard, MyCourseInProgressCard, UserCertificationCompleted, UserCertificationInProgress, + UserCertificationProgressStatus, } from '../../../learn-lib' import { LEARN_PATHS } from '../../../learn.routes' +import { CardsSlider } from '../cards-slider' import styles from './ProgressAction.module.scss' @@ -19,6 +22,12 @@ interface ProgressActionProps { userInProgressCertifications: ReadonlyArray } +function isCompleted(cert: LearnUserCertificationProgress): boolean { + return cert.status === UserCertificationProgressStatus.completed +} + +const USER_PROGRESS_MAX_SLIDES_COUNT: number = 8 + const ProgressAction: FC = (props: ProgressActionProps) => { const { @@ -45,72 +54,56 @@ const ProgressAction: FC = (props: ProgressActionProps) => }, {} as unknown as { [key: string]: LearnCertification }) ), [allCertifications]) - // we only want to display the last course that was acted upon - const mostRecentIsCompleted: boolean = myCompletedCertifications?.[0]?.updatedAt > (myInProgressCertifications?.[0]?.updatedAt || 0) - - function renderInProgress(): JSX.Element { - - // if the most recently acted upon course is completed and not in progress, - // or there are no courses in progress, don't show this block - if (mostRecentIsCompleted || !myInProgressCertifications.length) { - return <> - } - - const courseToDisplay: UserCertificationInProgress = myInProgressCertifications[0] + const recentlyUpdatedCertifications: Array = orderBy([ + ...myCompletedCertifications, + ...myInProgressCertifications, + ], 'updatedAt', 'desc').slice(0, USER_PROGRESS_MAX_SLIDES_COUNT) + function renderInProgress(courseToDisplay: UserCertificationInProgress): JSX.Element { return ( - <> -
    -

    In progress

    - - {allMyLearningsLink} - -
    - - + ) } - function renderCompleted(): JSX.Element { + function renderCompleted(certToDisplay: UserCertificationCompleted): JSX.Element { + return ( + + ) + } - // if the most recently acted upon course is in progress rather than completed, - // or there are no completed courses, don't show this block - if (!mostRecentIsCompleted || !myCompletedCertifications.length) { - return <> + function renderCertificateCards(): Array { + if (!recentlyUpdatedCertifications.length) { + return [] } - const certToDisplay: UserCertificationCompleted = myCompletedCertifications[0] - - return ( - <> -
    -
    - -

    Congratulations!

    -
    - - {allMyLearningsLink} - -
    - - - ) + return recentlyUpdatedCertifications.map((cert) => ( + isCompleted(cert) + ? renderCompleted(cert as UserCertificationCompleted) + : renderInProgress(cert as UserCertificationInProgress) + )) } return ( <> - {renderInProgress()} - {renderCompleted()} +
    +

    My progress

    + + {allMyLearningsLink} + +
    + + {renderCertificateCards()} + {allMyLearningsLink} From dbeb9ad6f5e8bd3e04bbbed17366b4c2bb305034 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Fri, 16 Sep 2022 16:31:05 +0300 Subject: [PATCH 049/113] update default active slider --- .../learn/welcome/progress-block/cards-slider/CardsSlider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-ts/tools/learn/welcome/progress-block/cards-slider/CardsSlider.tsx b/src-ts/tools/learn/welcome/progress-block/cards-slider/CardsSlider.tsx index e41f99aab..d8b4f8f29 100644 --- a/src-ts/tools/learn/welcome/progress-block/cards-slider/CardsSlider.tsx +++ b/src-ts/tools/learn/welcome/progress-block/cards-slider/CardsSlider.tsx @@ -9,7 +9,7 @@ interface CardsSliderProps { } const CardsSlider: FC = (props: CardsSliderProps) => { - const [activeSlide, setActiveSlide]: [number, Dispatch>] = useState(2) + const [activeSlide, setActiveSlide]: [number, Dispatch>] = useState(0) const wrapSlides: (children: Array) => Array = (children: Array) => { return Children.map(children, (child, index) => ( From f4d55e54cad820df95f34230400da7da49bfcc72 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Fri, 16 Sep 2022 16:54:40 +0300 Subject: [PATCH 050/113] TCA-428 - add "completion suggestions" on course landing page --- .../CourseDetailsPage.module.scss | 6 ++++- .../course-details/CourseDetailsPage.tsx | 22 +++++++++++++++++++ .../courses-functions/learn-course.model.ts | 1 + 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src-ts/tools/learn/course-details/CourseDetailsPage.module.scss b/src-ts/tools/learn/course-details/CourseDetailsPage.module.scss index 96aa7ad71..605a7afe9 100644 --- a/src-ts/tools/learn/course-details/CourseDetailsPage.module.scss +++ b/src-ts/tools/learn/course-details/CourseDetailsPage.module.scss @@ -47,10 +47,14 @@ @extend .body-main; margin-top: $space-xxl; } - + p { margin: $space-sm 0; } + + :global(.details.mtop) { + margin-top: $space-xxl; + } } .coming-soon { diff --git a/src-ts/tools/learn/course-details/CourseDetailsPage.tsx b/src-ts/tools/learn/course-details/CourseDetailsPage.tsx index 756bbdfb1..856829c6c 100644 --- a/src-ts/tools/learn/course-details/CourseDetailsPage.tsx +++ b/src-ts/tools/learn/course-details/CourseDetailsPage.tsx @@ -99,6 +99,27 @@ const CourseDetailsPage: FC<{}> = () => { ) } + function getCompletionSuggestion(): ReactNode { + if (!course) { + return + } + + return progress?.status === UserCertificationProgressStatus.completed ? ( + <> + ) : ( + !!course.completionSuggestions?.length && ( + <> +

    Suggestions for completing this course

    + +

    ') }} + >
    + + ) + ) + } + function getFooter(): ReactNode { if (!resourceProvider) { return @@ -141,6 +162,7 @@ const CourseDetailsPage: FC<{}> = () => {
    {getDescription()} + {getCompletionSuggestion()}
    diff --git a/src-ts/tools/learn/learn-lib/courses-provider/courses-functions/learn-course.model.ts b/src-ts/tools/learn/learn-lib/courses-provider/courses-functions/learn-course.model.ts index 34a4e2442..b9c7010a4 100644 --- a/src-ts/tools/learn/learn-lib/courses-provider/courses-functions/learn-course.model.ts +++ b/src-ts/tools/learn/learn-lib/courses-provider/courses-functions/learn-course.model.ts @@ -4,6 +4,7 @@ import { LearnModule } from '../../lesson-provider' export interface LearnCourse extends LearnModelBase { certification: string certificationId: string + completionSuggestions: Array estimatedCompletionTime: { units: string value: number From 40ef0defd5bcc62fdd997a38d569fc49d6a691c3 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Mon, 19 Sep 2022 14:00:17 +0300 Subject: [PATCH 051/113] GAME-107 and GAME-99 --- src-ts/lib/table/Table.module.scss | 9 +++ src-ts/lib/table/Table.tsx | 22 +++++- src-ts/lib/table/table-column.model.ts | 1 + .../lib/models/badge.model.ts | 9 +++ .../pages/badge-detail/BadgeDetailPage.tsx | 1 + .../pages/badge-listing/BadgeListingPage.tsx | 76 +++++++++---------- .../BadgeActionRenderer.module.scss | 27 +++++++ .../BadgeActionRenderer.tsx | 19 +++++ .../badge-action-renderer/index.ts | 1 + .../badge-listing-table.config.tsx | 22 ++++++ .../BadgeListingNameRenderer.module.scss | 28 +++++++ .../BadgeListingNameRenderer.tsx | 14 ++++ .../badge-name-renderer/index.ts | 1 + 13 files changed, 187 insertions(+), 43 deletions(-) create mode 100644 src-ts/tools/gamification-admin/lib/models/badge.model.ts create mode 100644 src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.module.scss create mode 100644 src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.tsx create mode 100644 src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/index.ts create mode 100644 src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-listing-table.config.tsx create mode 100644 src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.module.scss create mode 100644 src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.tsx create mode 100644 src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/index.ts diff --git a/src-ts/lib/table/Table.module.scss b/src-ts/lib/table/Table.module.scss index eeb951071..5582e4c7c 100644 --- a/src-ts/lib/table/Table.module.scss +++ b/src-ts/lib/table/Table.module.scss @@ -61,6 +61,10 @@ margin-right: -29px; } } + + &.centerHeader { + justify-content: center; + } } .tooltip { @@ -90,3 +94,8 @@ .tootlipBody { min-width: 200px; } + +.loadBtnWrap { + display: flex; + justify-content: center; +} \ No newline at end of file diff --git a/src-ts/lib/table/Table.tsx b/src-ts/lib/table/Table.tsx index 25c439700..7858bb476 100644 --- a/src-ts/lib/table/Table.tsx +++ b/src-ts/lib/table/Table.tsx @@ -1,6 +1,7 @@ import classNames from 'classnames' import { Dispatch, MouseEvent, SetStateAction, useEffect, useState } from 'react' +import { Button, ButtonSize, ButtonStyle } from '../button' import { Sort } from '../pagination' import '../styles/_includes.scss' import { IconOutline } from '../svgs' @@ -15,7 +16,13 @@ import styles from './Table.module.scss' interface TableProps { readonly columns: ReadonlyArray> readonly data: ReadonlyArray + readonly loadMoreBtnLabel?: string + readonly loadMoreBtnSize?: ButtonSize + readonly loadMoreBtnStyle?: ButtonStyle + readonly moreToLoad?: boolean + readonly onLoadMoreClick?: (data: T) => void readonly onRowClick?: (data: T) => void + readonly onToggleSort?: (sort: Sort | undefined) => void } interface DefaultSortDirectionMap { @@ -75,6 +82,11 @@ const Table: (props: TableProps) = fieldName, } setSort(newSort) + + // call the callback to notify parent for sort update + if (props.onToggleSort) { + props.onToggleSort(newSort) + } } const headerRow: Array = props.columns @@ -83,12 +95,13 @@ const Table: (props: TableProps) = const isCurrentlySorted: boolean = isSortable && col.propertyName === sort?.fieldName const colorClass: string = isCurrentlySorted ? 'black-100' : 'black-60' const sortableClass: string | undefined = isSortable ? styles.sortable : undefined + const centerClass: string | undefined = col.centerHeader ? styles.centerHeader : undefined return ( -
    +
    {col.label} {!!col.tooltip && (
    @@ -136,7 +149,7 @@ const Table: (props: TableProps) = // return the entire row return ( @@ -158,6 +171,11 @@ const Table: (props: TableProps) = {rowCells} + { + props.moreToLoad &&
    +
    + }
    ) } diff --git a/src-ts/lib/table/table-column.model.ts b/src-ts/lib/table/table-column.model.ts index 4323da523..6d7ddb0c9 100644 --- a/src-ts/lib/table/table-column.model.ts +++ b/src-ts/lib/table/table-column.model.ts @@ -1,6 +1,7 @@ import { TableCellType } from './table-cell.type' export interface TableColumn { + readonly centerHeader?: boolean readonly defaultSortDirection?: 'asc' | 'desc' readonly isDefaultSort?: boolean readonly label?: string diff --git a/src-ts/tools/gamification-admin/lib/models/badge.model.ts b/src-ts/tools/gamification-admin/lib/models/badge.model.ts new file mode 100644 index 000000000..c88efeb78 --- /dev/null +++ b/src-ts/tools/gamification-admin/lib/models/badge.model.ts @@ -0,0 +1,9 @@ +export interface Badge { + active: boolean + badge_description: string + badge_image_url: string + badge_name: string + badge_status: string + id: string + organization_id: string +} diff --git a/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx b/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx index c58a04227..1e84dc1ed 100644 --- a/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx @@ -7,6 +7,7 @@ import { toolTitle } from '../../GamificationAdmin' import styles from './BadgeDetailPage.module.scss' const BadgeDetailPage: FC = () => { + // TDOD: use whit GAME-78 // const { id: badgeID } : { badgeID: string } = useParams() const breadcrumb: Array = useMemo(() => [ { name: toolTitle, url: baseUrl }, diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx index ab8013099..08dc7c2be 100644 --- a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx @@ -1,32 +1,51 @@ -import { FC, useState } from 'react' +import { flatten, map } from 'lodash' +import { Dispatch, FC, SetStateAction, useState } from 'react' import { NavigateFunction, useNavigate } from 'react-router-dom' // tslint:disable-next-line import useSWRInfinite from 'swr/infinite' -import { Button, ButtonProps, ContentLayout, IconOutline, LoadingSpinner } from '../../../../lib' +import { ButtonProps, ContentLayout, LoadingSpinner, Sort, Table, TableColumn } from '../../../../lib' import { GamificationConfig } from '../../config' import { baseUrl } from '../../gamification-admin.routes' import getDataSource from '../../lib/hooks/getDataSource' +import { Badge } from '../../lib/models/badge.model' +import { badgeListingColumns } from './badge-listing-table/badge-listing-table.config' import styles from './BadgeListingPage.module.scss' const BadgeListingPage: FC = () => { const [order, setOrder]: any = useState({ by: 'badge_name', type: 'asc' }) const navigate: NavigateFunction = useNavigate() const dataSource: string = getDataSource() + + // server-side pagination hook const getKey: any = (pageIndex: any, previousPageData: any) => { if (previousPageData && !previousPageData.rows.length) { return undefined } // reached the end return `${dataSource}/badges?organization_id=${GamificationConfig.ORG_ID}&limit=12&offset=${pageIndex * 12}&order_by=${order.by}&order_type=${order.type}` } const { data: badges, size, setSize }: any = useSWRInfinite(getKey, { revalidateFirstPage: false }) - const loadedCnt: any = badges?.reduce((ps: any, a: any) => ps + a.rows.length, 0) - const onOrderClick: any = () => { + + const tableData: Array = flatten(map(badges, page => page.rows)) // flatten version of badges paginated data + const loadedCnt: any = badges?.reduce((ps: any, a: any) => ps + a.rows.length, 0) // how much data is loaded so far + + // listing table config + const [columns]: [ + ReadonlyArray>, + Dispatch>>>, + ] + = useState>>([...badgeListingColumns]) + + // on sort toggle callback + const onOrderClick: any = (sort: Sort) => { setOrder({ - by: order.by, - type: order.type === 'asc' ? 'desc' : 'asc', + by: sort.fieldName, + type: sort.direction, }) } + // on load more callback + const onLoadMoreClick: any = () => setSize(size + 1) + // header button config const buttonConfig: ButtonProps = { label: 'Create New Badge', onClick: () => navigate(`${baseUrl}/create-badge`), @@ -40,41 +59,16 @@ const BadgeListingPage: FC = () => { buttonConfig={buttonConfig} >
    -
    -
    - BADGE NAME - { - order.type === 'asc' ? ( -
    -
    ACTIONS
    -
    -
    - { - badges.map((page: any) => page.rows.map((badge: any) =>
    -
    - {badge.badge_name} -

    {badge.badge_name}

    -
    -
    -
    -
    - )) - } -
    - { - badges[0].count !== loadedCnt &&
    -
    - } + ) diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.module.scss b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.module.scss new file mode 100644 index 000000000..0ce6473f5 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.module.scss @@ -0,0 +1,27 @@ +@import "../../../../../../lib/styles/includes"; +@import "../../../../../../lib/styles/variables"; + +.badge-actions { + display: flex; + align-items: center; + justify-content: center; + padding-top: $space-lg; + + @include ltemd { + flex-direction: column; + align-items: flex-end; + } + + a { + margin-right: $space-sm; + + @include ltemd { + margin-right: 0; + margin-bottom: $space-sm; + } + + &:last-child { + margin-right: 0; + } + } +} \ No newline at end of file diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.tsx new file mode 100644 index 000000000..33972e587 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.tsx @@ -0,0 +1,19 @@ +import { Button, useCheckIsMobile } from '../../../../../../lib' +import { baseUrl } from '../../../../gamification-admin.routes' +import { Badge } from '../../../../lib/models/badge.model' + +import styles from './BadgeActionRenderer.module.scss' + +function BadgeActionRenderer(badge: Badge): JSX.Element { + const isMobile: boolean = useCheckIsMobile() + + return ( +
    +
    + ) +} + +export default BadgeActionRenderer diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/index.ts b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/index.ts new file mode 100644 index 000000000..8324241f1 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/index.ts @@ -0,0 +1 @@ +export { default as BadgeActionRenderer } from './BadgeActionRenderer' diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-listing-table.config.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-listing-table.config.tsx new file mode 100644 index 000000000..ab9667ebf --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-listing-table.config.tsx @@ -0,0 +1,22 @@ +import { TableColumn } from '../../../../../lib' +import { Badge } from '../../../lib/models/badge.model' + +import { BadgeActionRenderer } from './badge-action-renderer' +import { BadgeListingNameRenderer } from './badge-name-renderer' + +export const badgeListingColumns: ReadonlyArray> = [ + { + defaultSortDirection: 'asc', + isDefaultSort: true, + label: 'Badge Name', + propertyName: 'badge_name', + renderer: BadgeListingNameRenderer, + type: 'element', + }, + { + centerHeader: true, + label: 'Actions', + renderer: BadgeActionRenderer, + type: 'action', + }, +] diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.module.scss b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.module.scss new file mode 100644 index 000000000..0bbb9a231 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.module.scss @@ -0,0 +1,28 @@ +@import "../../../../../../lib/styles/includes"; +@import "../../../../../../lib/styles/variables"; + +.badge { + display: flex; + align-items: center; + + .badge-image { + width: 48px; + height: 48px; + margin-right: $space-xl; + } + + .badge-image-disabled { + width: 48px; + height: 48px; + margin-right: $space-xl; + opacity: 0.5; + filter: grayscale(1); + } + + .badge-name { + font-size: 16px; + font-weight: 700; + line-height: 24px; + color: $black-100; + } +} \ No newline at end of file diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.tsx new file mode 100644 index 000000000..333aed2e7 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.tsx @@ -0,0 +1,14 @@ +import { Badge } from '../../../../lib/models/badge.model' + +import styles from './BadgeListingNameRenderer.module.scss' + +function BadgeListingNameRenderer(badge: Badge): JSX.Element { + return ( +
    + {badge.badge_name} +

    {badge.badge_name}

    +
    + ) +} + +export default BadgeListingNameRenderer diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/index.ts b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/index.ts new file mode 100644 index 000000000..dac5de575 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/index.ts @@ -0,0 +1 @@ +export { default as BadgeListingNameRenderer } from './BadgeListingNameRenderer' From 3594327d88e8443a682e2e9d10b5db90a74894bd Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 19 Sep 2022 15:22:23 +0300 Subject: [PATCH 052/113] TCA-441 - implement form input select --- .../lib/form/form-groups/form-input/index.ts | 1 + .../input-select/InputSelect.module.scss | 45 +++++++++ .../form-input/input-select/InputSelect.tsx | 99 +++++++++++++++++++ .../form-input/input-select/index.ts | 1 + .../input-wrapper/InputWrapper.module.scss | 1 + .../form-input/input-wrapper/InputWrapper.tsx | 11 ++- src-ts/lib/form/index.ts | 2 +- 7 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss create mode 100644 src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx create mode 100644 src-ts/lib/form/form-groups/form-input/input-select/index.ts diff --git a/src-ts/lib/form/form-groups/form-input/index.ts b/src-ts/lib/form/form-groups/form-input/index.ts index 3f5dfa642..46f16f8c4 100644 --- a/src-ts/lib/form/form-groups/form-input/index.ts +++ b/src-ts/lib/form/form-groups/form-input/index.ts @@ -1,5 +1,6 @@ export * from './form-input-autcomplete-option.enum' export * from './input-rating' +export * from './input-select' export * from './input-text' export * from './input-textarea' export { inputOptional } from './input-wrapper' diff --git a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss new file mode 100644 index 000000000..4911da867 --- /dev/null +++ b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss @@ -0,0 +1,45 @@ +@import '../../../../styles/includes'; + +.selected { + display: flex; + align-items: center; + margin-top: $space-xs; + cursor: pointer; + color: $black-100; + + &-icon { + margin-left: auto; + padding: $border-xs 0; + color: $turq-160; + > svg { + @include icon-size(14); + } + } +} + +.select-menu { + position: absolute; + top: calc(100% - 2px); + left: -1px; + right: -1px; + background: $tc-white; + border: $border-xs solid $black-40; + border-radius: 0 0 $space-xs $space-xs; + padding: $space-sm 0; +} + +.select-option { + font-weight: normal; + color: $black-100; + padding: $space-sm $space-lg; + + &:hover:global(:not(.selected)) { + background: $turq-160; + color: $tc-white; + cursor: pointer; + } + + &:global(.selected) { + font-weight: bold; + } +} diff --git a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx new file mode 100644 index 000000000..b538a90c1 --- /dev/null +++ b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx @@ -0,0 +1,99 @@ +import classNames from 'classnames' +import { + ChangeEvent, + FC, + ReactNode, + useState, + Dispatch, + SetStateAction, + MutableRefObject, + useRef, +} from 'react' +import { useClickOutside } from '../../../../hooks' +import { IconOutline } from '../../../../svgs' +import { InputWrapper } from '../input-wrapper' + +import styles from './InputSelect.module.scss' + +export interface InputSelectOption { + value: string + label?: ReactNode +} + +interface InputSelectProps { + readonly dirty?: boolean + readonly disabled?: boolean + readonly error?: string + readonly hideInlineErrors?: boolean + readonly name: string + readonly onChange: (event: ChangeEvent) => void + readonly tabIndex?: number + readonly value?: string + readonly options: Array + readonly label?: string + readonly hint?: string +} + +const InputSelect: FC = (props: InputSelectProps) => { + const triggerRef: MutableRefObject = useRef(undefined) + const [menuIsVisible, setMenuIsVisible]: [boolean, Dispatch>] = useState(false) + + const selectedOption = props.options.find(option => option.value === props.value); + + const label = (option?: InputSelectOption) => option ? option.label ?? option.value : '' + + const toggleMenu = () => setMenuIsVisible((wasVisible) => !wasVisible) + + const select = (option: InputSelectOption) => () => { + props.onChange({ + target: {value: option.value} , + } as unknown as ChangeEvent) + toggleMenu() + } + + useClickOutside(triggerRef.current, () => setMenuIsVisible(false)) + + return ( + +
    + {label(selectedOption)} + + + +
    + + {menuIsVisible && ( +
    + {props.options.map((option) => ( +
    + {label(option)} +
    + ))} +
    + )} + +
    + ) +} + +export default InputSelect diff --git a/src-ts/lib/form/form-groups/form-input/input-select/index.ts b/src-ts/lib/form/form-groups/form-input/input-select/index.ts new file mode 100644 index 000000000..605f28a24 --- /dev/null +++ b/src-ts/lib/form/form-groups/form-input/input-select/index.ts @@ -0,0 +1 @@ +export { default as InputSelect } from './InputSelect' diff --git a/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.module.scss b/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.module.scss index 6b212bb53..4052802ad 100644 --- a/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.module.scss +++ b/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.module.scss @@ -30,6 +30,7 @@ $error-line-height: 14px; box-sizing: border-box; border-radius: $space-xs; margin-bottom: calc($error-line-height + $space-xs); + position: relative; &.rating { border-color: transparent; diff --git a/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx b/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx index dff63ca4d..b51d6f32e 100644 --- a/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames' -import { Dispatch, FC, ReactNode, SetStateAction, useState } from 'react' +import { Dispatch, forwardRef, ForwardRefExoticComponent, ReactNode, SetStateAction, useState } from 'react' import { IconSolid } from '../../../../svgs' @@ -16,11 +16,11 @@ interface InputWrapperProps { readonly hideInlineErrors?: boolean readonly hint?: string readonly label: string | JSX.Element - readonly tabIndex: number + readonly tabIndex?: number readonly type: 'checkbox' | 'password' | 'rating' | 'text' | 'textarea' } -const InputWrapper: FC = (props: InputWrapperProps) => { +const InputWrapper: ForwardRefExoticComponent = forwardRef((props: InputWrapperProps, ref) => { const [focusStyle, setFocusStyle]: [string | undefined, Dispatch>] = useState() @@ -44,7 +44,8 @@ const InputWrapper: FC = (props: InputWrapperProps) => { return (
    = (props: InputWrapperProps) => { )}
    ) -} +}) export default InputWrapper diff --git a/src-ts/lib/form/index.ts b/src-ts/lib/form/index.ts index 4cc7d9d69..cc059c6f6 100644 --- a/src-ts/lib/form/index.ts +++ b/src-ts/lib/form/index.ts @@ -8,5 +8,5 @@ export { } from './form-functions' export * from './form-input.model' export * from './form-group.model' -export { inputOptional, FormInputAutocompleteOption } from './form-groups' +export * from './form-groups/form-input' export * from './validator-functions' From efa1d12652cb879180125335a1581be8a2658200 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 19 Sep 2022 16:28:55 +0300 Subject: [PATCH 053/113] TCA-441 - implement certificates sorting on tca landing page --- .../all-certifications-provider-data.model.ts | 2 +- .../all-certifications-sort-options.ts | 14 ++++ .../all-certifications.provider.tsx | 70 ++++++++++++++----- .../all-certifications-provider/index.ts | 1 + .../learn/welcome/WelcomePage.module.scss | 41 +++++++++-- src-ts/tools/learn/welcome/WelcomePage.tsx | 49 +++++++++++-- 6 files changed, 147 insertions(+), 30 deletions(-) create mode 100644 src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts index cc5d28762..68fa10988 100755 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts @@ -2,8 +2,8 @@ import { LearnCertification } from './all-certifications-functions' export interface AllCertificationsProviderData { certification?: LearnCertification + allCertifications: Array certifications: Array - certificationsCount: number loading: boolean ready: boolean } diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts new file mode 100644 index 000000000..41351850f --- /dev/null +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts @@ -0,0 +1,14 @@ +export const ALL_CERTIFICATIONS_SORT_ENUM = { + createdAt: 'Newest', + title: 'Title', + category: 'Category', +}; + +export type ALL_CERTIFICATIONS_SORT_FIELD_TYPE = keyof typeof ALL_CERTIFICATIONS_SORT_ENUM; + +export const ALL_CERTIFICATIONS_SORT_OPTIONS: Array<{value: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, label: string}> = Object.entries( + ALL_CERTIFICATIONS_SORT_ENUM +).map(([value, label]) => ({value: value as ALL_CERTIFICATIONS_SORT_FIELD_TYPE, label})); + +export const ALL_CERTIFICATIONS_SORT_FIELDS: Array = Object.keys(ALL_CERTIFICATIONS_SORT_ENUM) as Array +export const ALL_CERTIFICATIONS_DEFAULT_SORT: ALL_CERTIFICATIONS_SORT_FIELD_TYPE = ALL_CERTIFICATIONS_SORT_OPTIONS[0].value; diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx index 5bbb14210..b0d8126bb 100644 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx @@ -1,10 +1,21 @@ -import { Dispatch, SetStateAction, useEffect, useState } from 'react' +import { orderBy } from 'lodash' +import { Dispatch, SetStateAction, useEffect, useState, useRef } from 'react' import { allCertificationsGetAsync, LearnCertification } from './all-certifications-functions' import { AllCertificationsProviderData } from './all-certifications-provider-data.model' +import { ALL_CERTIFICATIONS_DEFAULT_SORT, ALL_CERTIFICATIONS_SORT_FIELD_TYPE } from './all-certifications-sort-options' + +type SORT_DIRECTION = 'asc'|'desc' +const DEFAULT_SORT_DIRECTION: SORT_DIRECTION = 'desc' + interface CertificationsAllProviderOptions { enabled?: boolean + sort?: { + direction: SORT_DIRECTION, + field: ALL_CERTIFICATIONS_SORT_FIELD_TYPE + }, + filter?: undefined|'data-science'|'web-development'|'backend-development' } export function useAllCertifications( @@ -12,16 +23,47 @@ export function useAllCertifications( certificationId?: string, options?: CertificationsAllProviderOptions ): AllCertificationsProviderData { + const sort = useRef({ + field: ALL_CERTIFICATIONS_DEFAULT_SORT, + direction: DEFAULT_SORT_DIRECTION, + ...options?.sort, + }); + // const filter = useRef(options?.filter); const [state, setState]: [AllCertificationsProviderData, Dispatch>] = useState({ + allCertifications: [], certifications: [], - certificationsCount: 0, loading: false, ready: false, }) + function sortCertifications(certificates: Array, sortField: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, sortDir: SORT_DIRECTION) { + return orderBy([...certificates], sortField, sortDir) + } + + function sortCertificates() { + setState((prevState) => ({ + ...prevState, + certifications: sortCertifications( + prevState.allCertifications, + sort.current.field, + sort.current.direction, + ), + })) + } + + if (options?.sort && (sort.current?.direction !== options?.sort?.direction || sort.current?.field !== options?.sort?.field)) { + sort.current = { + field: options?.sort?.field ?? ALL_CERTIFICATIONS_DEFAULT_SORT, + direction: options?.sort?.direction ?? DEFAULT_SORT_DIRECTION, + }; + + // wait to exit current render loop before triggering a new state update + setTimeout(sortCertificates) + } + useEffect(() => { setState((prevState) => ({ ...prevState, @@ -34,23 +76,17 @@ export function useAllCertifications( allCertificationsGetAsync(provider, certificationId) .then((certifications) => { - const certs: { - certification: LearnCertification; - certifications?: undefined; - } | { - certification?: undefined; - certifications: Array; - } = certificationId - ? { - certification: certifications as unknown as LearnCertification, - } - : { - certifications: [...certifications], - } + const sortedCertifications = sortCertifications( + certifications, + sort.current.field, + sort.current.direction, + ) + setState((prevState) => ({ ...prevState, - ...certs, - certificationsCount: certifications.length, + certifications: certificationId ? [] : sortedCertifications, + certification: !certificationId ? undefined : certifications as unknown as LearnCertification, + allCertifications: certificationId ? [] : [...certifications], loading: false, ready: true, })) diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts index f8e61f839..1fb9b9c4a 100755 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts @@ -1,3 +1,4 @@ export * from './all-certifications-functions' export * from './all-certifications-provider-data.model' +export * from './all-certifications-sort-options' export * from './all-certifications.provider' diff --git a/src-ts/tools/learn/welcome/WelcomePage.module.scss b/src-ts/tools/learn/welcome/WelcomePage.module.scss index c00834ead..97ee4655c 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.module.scss +++ b/src-ts/tools/learn/welcome/WelcomePage.module.scss @@ -4,7 +4,7 @@ :global(.hero-card-col) { width: 43.5%; max-width: 600px; - + @include ltemd { width: 100%; max-width: none; @@ -15,7 +15,7 @@ .courses-section { padding: $space-xxxxl 0; position: relative; - + @include ltemd { padding-top: $space-xxl; } @@ -38,12 +38,43 @@ @include ltelg { grid-template-columns: repeat(2, 1fr); } - + @media (max-width: 576px) { grid-template-columns: repeat(1, 1fr); } - + @include ltemd { margin-top: $space-xxl; } -} \ No newline at end of file +} + +.courses-list-header { + display: flex; + align-items: center; + + > h3 { + display: flex; + align-items: center; + gap: $space-sm; + } +} + +.badge { + font-family: $font-roboto; + background: $blue-100; + + padding: 0 $space-sm; + border-radius: 50px; + color: $tc-white; +} + +.courses-list-filters { + display: flex; + margin-left: auto; + + gap: $space-xxl; + + > * { + min-width: 326px; + } +} diff --git a/src-ts/tools/learn/welcome/WelcomePage.tsx b/src-ts/tools/learn/welcome/WelcomePage.tsx index 12008470f..1b674c205 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.tsx +++ b/src-ts/tools/learn/welcome/WelcomePage.tsx @@ -1,10 +1,13 @@ import classNames from 'classnames' -import { FC } from 'react' +import { Dispatch, FC, SetStateAction, useState } from 'react' -import { ContentLayout, LoadingSpinner, Portal } from '../../../lib' +import { ContentLayout, InputSelect, LoadingSpinner, Portal } from '../../../lib' import '../../../lib/styles/index.scss' import { AllCertificationsProviderData, + ALL_CERTIFICATIONS_DEFAULT_SORT, + ALL_CERTIFICATIONS_SORT_FIELD_TYPE, + ALL_CERTIFICATIONS_SORT_OPTIONS, useAllCertifications, UserCertificationsProviderData, useUserCertifications, @@ -17,7 +20,21 @@ import styles from './WelcomePage.module.scss' const WelcomePage: FC<{}> = () => { - const allCertsData: AllCertificationsProviderData = useAllCertifications() + const [sortField, setSortField]: [ + ALL_CERTIFICATIONS_SORT_FIELD_TYPE, + Dispatch> + ] = useState(ALL_CERTIFICATIONS_DEFAULT_SORT) + + const allCertsData: AllCertificationsProviderData = useAllCertifications( + undefined, + undefined, + { + sort: { + direction: sortField === 'createdAt' ? 'desc' : 'asc', + field: sortField + } + } + ) const userCertsData: UserCertificationsProviderData = useUserCertifications() const coursesReady: boolean = allCertsData.ready && userCertsData.ready @@ -42,7 +59,7 @@ const WelcomePage: FC<{}> = () => { theme='light' > = () => {
    -

    Courses Available

    +
    +

    + Courses Available + + {allCertsData.certifications.length} + +

    + + +
    + setSortField(e.target.value as ALL_CERTIFICATIONS_SORT_FIELD_TYPE)} + name='sort-courses' + label='Sort by' + > +
    +
    {coursesReady && (
    {allCertsData.certifications - .map((certification) => ( + .map((certification, i) => ( From 8a1922d8d0da742da2f860dc96bfe5d06557879e Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 19 Sep 2022 16:51:28 +0300 Subject: [PATCH 054/113] add storage hook to keep state in sync with local storage --- src-ts/lib/hooks/index.ts | 1 + src-ts/lib/hooks/use-storage.hook.ts | 69 ++++++++++++++++++++++ src-ts/tools/learn/welcome/WelcomePage.tsx | 4 +- 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 src-ts/lib/hooks/use-storage.hook.ts diff --git a/src-ts/lib/hooks/index.ts b/src-ts/lib/hooks/index.ts index a71c9300e..920935edc 100644 --- a/src-ts/lib/hooks/index.ts +++ b/src-ts/lib/hooks/index.ts @@ -1,4 +1,5 @@ export * from './use-check-is-mobile.hook' export * from './use-click-outside.hook' export * from './use-on-hover-element.hook' +export * from './use-storage.hook' export * from './use-window-size.hook' diff --git a/src-ts/lib/hooks/use-storage.hook.ts b/src-ts/lib/hooks/use-storage.hook.ts new file mode 100644 index 000000000..282601eeb --- /dev/null +++ b/src-ts/lib/hooks/use-storage.hook.ts @@ -0,0 +1,69 @@ +import { Dispatch, SetStateAction, useCallback, useState } from 'react' + +type StorageTypes = 'localStorage' | 'sessionStorage' + +export function useStorage( + storageType: StorageTypes, + storageKey: string, + initialValue?: T +): [T, Dispatch>] { + const storage: Storage = window[storageType] + + const readStoredValue: () => T = useCallback(() => { + try { + // Get from local storage by key + const item: string | null = storage.getItem(storageKey) + // Parse stored json or if none return initialValue + return item ? JSON.parse(item) : initialValue + } catch (error) { + // If error also return value + return initialValue + } + }, [storage, storageKey, initialValue]) + + // State to store our value + // Pass initial state function to useState so logic is only executed once + const [storedValue, setStoredValue]: [T, Dispatch>] = useState(readStoredValue()) + + // Return a wrapped version of useState's setter function that + // persists the new value to local or session storage. + const setValue: Dispatch> = useCallback((value: T) => { + try { + // Allow value to be a function so we have same API as useState + setStoredValue((storedv: T) => { + const valueToStore: T = value instanceof Function ? value(storedv) : value + + if (valueToStore === undefined) { + storage.removeItem(storageKey) + } else { + // Save to local storage + storage.setItem(storageKey, JSON.stringify(valueToStore)) + } + + return valueToStore + }) + } catch (error) { + // A more advanced implementation would handle the error case + // tslint:disable-next-line:no-console + console.error(error) + } + }, [storage, storageKey]) as Dispatch> + + return [storedValue, setValue] +} + +export const useLocalStorage: ( + key: string, + initialValue?: T +) => [T, Dispatch>] = ( + key: string, + initialValue?: T +) => useStorage('localStorage', key, initialValue) + +export const useSessionStorage: ( + key: string, + initialValue?: T +) => [T, Dispatch>] = ( + key: string, + initialValue?: T +) => useStorage('sessionStorage', key, initialValue) diff --git a/src-ts/tools/learn/welcome/WelcomePage.tsx b/src-ts/tools/learn/welcome/WelcomePage.tsx index 1b674c205..aedb00554 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.tsx +++ b/src-ts/tools/learn/welcome/WelcomePage.tsx @@ -1,7 +1,7 @@ import classNames from 'classnames' import { Dispatch, FC, SetStateAction, useState } from 'react' -import { ContentLayout, InputSelect, LoadingSpinner, Portal } from '../../../lib' +import { ContentLayout, InputSelect, LoadingSpinner, Portal, useLocalStorage } from '../../../lib' import '../../../lib/styles/index.scss' import { AllCertificationsProviderData, @@ -23,7 +23,7 @@ const WelcomePage: FC<{}> = () => { const [sortField, setSortField]: [ ALL_CERTIFICATIONS_SORT_FIELD_TYPE, Dispatch> - ] = useState(ALL_CERTIFICATIONS_DEFAULT_SORT) + ] = useLocalStorage('tca-welcome-sort-certs', ALL_CERTIFICATIONS_DEFAULT_SORT) const allCertsData: AllCertificationsProviderData = useAllCertifications( undefined, From fcacff39defc5bed66aa19519104b0a407ab161b Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 19 Sep 2022 17:01:07 +0300 Subject: [PATCH 055/113] lint fixes --- .../form-input/input-select/InputSelect.tsx | 39 +++++++++++-------- .../all-certifications-provider-data.model.ts | 2 +- .../all-certifications-sort-options.ts | 18 +++++---- .../all-certifications.provider.tsx | 35 ++++++++++------- src-ts/tools/learn/welcome/WelcomePage.tsx | 7 ++-- 5 files changed, 57 insertions(+), 44 deletions(-) diff --git a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx index b538a90c1..602f09e48 100644 --- a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx @@ -1,14 +1,15 @@ import classNames from 'classnames' import { ChangeEvent, + Dispatch, FC, + MutableRefObject, ReactNode, - useState, - Dispatch, SetStateAction, - MutableRefObject, useRef, + useState, } from 'react' + import { useClickOutside } from '../../../../hooks' import { IconOutline } from '../../../../svgs' import { InputWrapper } from '../input-wrapper' @@ -16,8 +17,8 @@ import { InputWrapper } from '../input-wrapper' import styles from './InputSelect.module.scss' export interface InputSelectOption { - value: string label?: ReactNode + value: string } interface InputSelectProps { @@ -25,26 +26,28 @@ interface InputSelectProps { readonly disabled?: boolean readonly error?: string readonly hideInlineErrors?: boolean + readonly hint?: string + readonly label?: string readonly name: string readonly onChange: (event: ChangeEvent) => void + readonly options: Array readonly tabIndex?: number readonly value?: string - readonly options: Array - readonly label?: string - readonly hint?: string } const InputSelect: FC = (props: InputSelectProps) => { const triggerRef: MutableRefObject = useRef(undefined) const [menuIsVisible, setMenuIsVisible]: [boolean, Dispatch>] = useState(false) - const selectedOption = props.options.find(option => option.value === props.value); + const selectedOption: InputSelectOption | undefined = props.options.find(option => option.value === props.value) - const label = (option?: InputSelectOption) => option ? option.label ?? option.value : '' + const label: (option: InputSelectOption) => ReactNode = (option?: InputSelectOption) => ( + option ? option.label ?? option.value : '' + ) - const toggleMenu = () => setMenuIsVisible((wasVisible) => !wasVisible) + const toggleMenu: () => void = () => setMenuIsVisible((wasVisible) => !wasVisible) - const select = (option: InputSelectOption) => () => { + const select: (option: InputSelectOption) => () => void = (option: InputSelectOption) => () => { props.onChange({ target: {value: option.value} , } as unknown as ChangeEvent) @@ -65,12 +68,14 @@ const InputSelect: FC = (props: InputSelectProps) => { hideInlineErrors={props.hideInlineErrors} ref={triggerRef} > -
    - {label(selectedOption)} - - - -
    + {selectedOption && ( +
    + {label(selectedOption)} + + + +
    + )} {menuIsVisible && (
    diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts index 68fa10988..902cfedea 100755 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts @@ -1,8 +1,8 @@ import { LearnCertification } from './all-certifications-functions' export interface AllCertificationsProviderData { - certification?: LearnCertification allCertifications: Array + certification?: LearnCertification certifications: Array loading: boolean ready: boolean diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts index 41351850f..1a3ca572d 100644 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts @@ -1,14 +1,18 @@ -export const ALL_CERTIFICATIONS_SORT_ENUM = { +export const ALL_CERTIFICATIONS_SORT_ENUM: { + category: string, + createdAt: string, + title: string, +} = { + category: 'Category', createdAt: 'Newest', title: 'Title', - category: 'Category', -}; +} -export type ALL_CERTIFICATIONS_SORT_FIELD_TYPE = keyof typeof ALL_CERTIFICATIONS_SORT_ENUM; +export type ALL_CERTIFICATIONS_SORT_FIELD_TYPE = keyof typeof ALL_CERTIFICATIONS_SORT_ENUM -export const ALL_CERTIFICATIONS_SORT_OPTIONS: Array<{value: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, label: string}> = Object.entries( +export const ALL_CERTIFICATIONS_SORT_OPTIONS: Array<{label: string, value: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, }> = Object.entries( ALL_CERTIFICATIONS_SORT_ENUM -).map(([value, label]) => ({value: value as ALL_CERTIFICATIONS_SORT_FIELD_TYPE, label})); +).map(([value, label]) => ({value: value as ALL_CERTIFICATIONS_SORT_FIELD_TYPE, label})) export const ALL_CERTIFICATIONS_SORT_FIELDS: Array = Object.keys(ALL_CERTIFICATIONS_SORT_ENUM) as Array -export const ALL_CERTIFICATIONS_DEFAULT_SORT: ALL_CERTIFICATIONS_SORT_FIELD_TYPE = ALL_CERTIFICATIONS_SORT_OPTIONS[0].value; +export const ALL_CERTIFICATIONS_DEFAULT_SORT: ALL_CERTIFICATIONS_SORT_FIELD_TYPE = ALL_CERTIFICATIONS_SORT_OPTIONS[0].value diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx index b0d8126bb..9c7d02eda 100644 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx @@ -1,5 +1,5 @@ import { orderBy } from 'lodash' -import { Dispatch, SetStateAction, useEffect, useState, useRef } from 'react' +import { Dispatch, MutableRefObject, SetStateAction, useEffect, useRef, useState } from 'react' import { allCertificationsGetAsync, LearnCertification } from './all-certifications-functions' import { AllCertificationsProviderData } from './all-certifications-provider-data.model' @@ -8,14 +8,15 @@ import { ALL_CERTIFICATIONS_DEFAULT_SORT, ALL_CERTIFICATIONS_SORT_FIELD_TYPE } f type SORT_DIRECTION = 'asc'|'desc' const DEFAULT_SORT_DIRECTION: SORT_DIRECTION = 'desc' +interface CertificationsAllProviderSortOptions { + direction: SORT_DIRECTION, + field: ALL_CERTIFICATIONS_SORT_FIELD_TYPE +} interface CertificationsAllProviderOptions { enabled?: boolean - sort?: { - direction: SORT_DIRECTION, - field: ALL_CERTIFICATIONS_SORT_FIELD_TYPE - }, filter?: undefined|'data-science'|'web-development'|'backend-development' + sort?: CertificationsAllProviderSortOptions } export function useAllCertifications( @@ -23,11 +24,11 @@ export function useAllCertifications( certificationId?: string, options?: CertificationsAllProviderOptions ): AllCertificationsProviderData { - const sort = useRef({ - field: ALL_CERTIFICATIONS_DEFAULT_SORT, + const sort: MutableRefObject = useRef({ direction: DEFAULT_SORT_DIRECTION, + field: ALL_CERTIFICATIONS_DEFAULT_SORT, ...options?.sort, - }); + }) // const filter = useRef(options?.filter); const [state, setState]: @@ -39,11 +40,15 @@ export function useAllCertifications( ready: false, }) - function sortCertifications(certificates: Array, sortField: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, sortDir: SORT_DIRECTION) { + function sortCertifications( + certificates: Array, + sortField: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, + sortDir: SORT_DIRECTION + ): Array { return orderBy([...certificates], sortField, sortDir) } - function sortCertificates() { + function sortCertificates(): void { setState((prevState) => ({ ...prevState, certifications: sortCertifications( @@ -56,9 +61,9 @@ export function useAllCertifications( if (options?.sort && (sort.current?.direction !== options?.sort?.direction || sort.current?.field !== options?.sort?.field)) { sort.current = { - field: options?.sort?.field ?? ALL_CERTIFICATIONS_DEFAULT_SORT, direction: options?.sort?.direction ?? DEFAULT_SORT_DIRECTION, - }; + field: options?.sort?.field ?? ALL_CERTIFICATIONS_DEFAULT_SORT, + } // wait to exit current render loop before triggering a new state update setTimeout(sortCertificates) @@ -76,7 +81,7 @@ export function useAllCertifications( allCertificationsGetAsync(provider, certificationId) .then((certifications) => { - const sortedCertifications = sortCertifications( + const sortedCertifications: Array = sortCertifications( certifications, sort.current.field, sort.current.direction, @@ -84,9 +89,9 @@ export function useAllCertifications( setState((prevState) => ({ ...prevState, - certifications: certificationId ? [] : sortedCertifications, - certification: !certificationId ? undefined : certifications as unknown as LearnCertification, allCertifications: certificationId ? [] : [...certifications], + certification: !certificationId ? undefined : certifications as unknown as LearnCertification, + certifications: certificationId ? [] : sortedCertifications, loading: false, ready: true, })) diff --git a/src-ts/tools/learn/welcome/WelcomePage.tsx b/src-ts/tools/learn/welcome/WelcomePage.tsx index aedb00554..c19cd12ee 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.tsx +++ b/src-ts/tools/learn/welcome/WelcomePage.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames' -import { Dispatch, FC, SetStateAction, useState } from 'react' +import { Dispatch, FC, SetStateAction } from 'react' import { ContentLayout, InputSelect, LoadingSpinner, Portal, useLocalStorage } from '../../../lib' import '../../../lib/styles/index.scss' @@ -31,8 +31,8 @@ const WelcomePage: FC<{}> = () => { { sort: { direction: sortField === 'createdAt' ? 'desc' : 'asc', - field: sortField - } + field: sortField, + }, } ) const userCertsData: UserCertificationsProviderData = useUserCertifications() @@ -78,7 +78,6 @@ const WelcomePage: FC<{}> = () => { -
    Date: Mon, 19 Sep 2022 18:12:21 +0300 Subject: [PATCH 056/113] TCA-441 - add certifications filter by category on landing page --- .../input-select/InputSelect.module.scss | 2 + .../all-certifications-sort-options.ts | 18 ---- .../all-certifications.provider.tsx | 83 ++++++++++--------- .../all-certifications-provider/index.ts | 1 - src-ts/tools/learn/welcome/WelcomePage.tsx | 49 +++++++++-- 5 files changed, 87 insertions(+), 66 deletions(-) delete mode 100644 src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts diff --git a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss index 4911da867..bf55e8a28 100644 --- a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss +++ b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss @@ -26,6 +26,8 @@ border: $border-xs solid $black-40; border-radius: 0 0 $space-xs $space-xs; padding: $space-sm 0; + max-height: 230px; + overflow: auto; } .select-option { diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts deleted file mode 100644 index 1a3ca572d..000000000 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const ALL_CERTIFICATIONS_SORT_ENUM: { - category: string, - createdAt: string, - title: string, -} = { - category: 'Category', - createdAt: 'Newest', - title: 'Title', -} - -export type ALL_CERTIFICATIONS_SORT_FIELD_TYPE = keyof typeof ALL_CERTIFICATIONS_SORT_ENUM - -export const ALL_CERTIFICATIONS_SORT_OPTIONS: Array<{label: string, value: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, }> = Object.entries( - ALL_CERTIFICATIONS_SORT_ENUM -).map(([value, label]) => ({value: value as ALL_CERTIFICATIONS_SORT_FIELD_TYPE, label})) - -export const ALL_CERTIFICATIONS_SORT_FIELDS: Array = Object.keys(ALL_CERTIFICATIONS_SORT_ENUM) as Array -export const ALL_CERTIFICATIONS_DEFAULT_SORT: ALL_CERTIFICATIONS_SORT_FIELD_TYPE = ALL_CERTIFICATIONS_SORT_OPTIONS[0].value diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx index 9c7d02eda..65ace83d9 100644 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx @@ -1,21 +1,22 @@ -import { orderBy } from 'lodash' +import { filter as filterBy, orderBy } from 'lodash' import { Dispatch, MutableRefObject, SetStateAction, useEffect, useRef, useState } from 'react' import { allCertificationsGetAsync, LearnCertification } from './all-certifications-functions' import { AllCertificationsProviderData } from './all-certifications-provider-data.model' -import { ALL_CERTIFICATIONS_DEFAULT_SORT, ALL_CERTIFICATIONS_SORT_FIELD_TYPE } from './all-certifications-sort-options' - -type SORT_DIRECTION = 'asc'|'desc' -const DEFAULT_SORT_DIRECTION: SORT_DIRECTION = 'desc' interface CertificationsAllProviderSortOptions { - direction: SORT_DIRECTION, - field: ALL_CERTIFICATIONS_SORT_FIELD_TYPE + direction: 'asc'|'desc', + field: keyof LearnCertification +} + +interface CertificationsAllProviderFilterOptions { + field: keyof LearnCertification, + value: string } interface CertificationsAllProviderOptions { enabled?: boolean - filter?: undefined|'data-science'|'web-development'|'backend-development' + filter?: CertificationsAllProviderFilterOptions sort?: CertificationsAllProviderSortOptions } @@ -24,12 +25,8 @@ export function useAllCertifications( certificationId?: string, options?: CertificationsAllProviderOptions ): AllCertificationsProviderData { - const sort: MutableRefObject = useRef({ - direction: DEFAULT_SORT_DIRECTION, - field: ALL_CERTIFICATIONS_DEFAULT_SORT, - ...options?.sort, - }) - // const filter = useRef(options?.filter); + const sort: MutableRefObject = useRef(options?.sort) + const filter: MutableRefObject = useRef(options?.filter) const [state, setState]: [AllCertificationsProviderData, Dispatch>] @@ -40,33 +37,46 @@ export function useAllCertifications( ready: false, }) - function sortCertifications( - certificates: Array, - sortField: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, - sortDir: SORT_DIRECTION + function getSortedCertifications( + certificates: Array ): Array { - return orderBy([...certificates], sortField, sortDir) + return !sort.current + ? certificates + : orderBy([...certificates], sort.current.field, sort.current.direction) } - function sortCertificates(): void { - setState((prevState) => ({ + function getFilteredCertifications( + certificates: Array + ): Array { + return !filter.current?.value + ? certificates + : filterBy([...certificates], {[filter.current.field]: filter.current.value}) + } + + function getFilteredAndSortedCertifications( + certificates: Array + ): Array { + return getSortedCertifications(getFilteredCertifications(certificates)) + } + + if (sort.current?.direction !== options?.sort?.direction || sort.current?.field !== options?.sort?.field) { + sort.current = options?.sort ? { ...options?.sort } : undefined + + // wait to exit current render loop before triggering a new state update + setTimeout(() => setState((prevState) => ({ ...prevState, - certifications: sortCertifications( - prevState.allCertifications, - sort.current.field, - sort.current.direction, - ), - })) + certifications: getFilteredAndSortedCertifications(prevState.allCertifications), + }))) } - if (options?.sort && (sort.current?.direction !== options?.sort?.direction || sort.current?.field !== options?.sort?.field)) { - sort.current = { - direction: options?.sort?.direction ?? DEFAULT_SORT_DIRECTION, - field: options?.sort?.field ?? ALL_CERTIFICATIONS_DEFAULT_SORT, - } + if (filter.current?.field !== options?.filter?.field || filter.current?.value !== options?.filter?.value) { + filter.current = options?.filter ? { ...options?.filter } : undefined // wait to exit current render loop before triggering a new state update - setTimeout(sortCertificates) + setTimeout(() => setState((prevState) => ({ + ...prevState, + certifications: getFilteredAndSortedCertifications(prevState.allCertifications), + }))) } useEffect(() => { @@ -81,11 +91,8 @@ export function useAllCertifications( allCertificationsGetAsync(provider, certificationId) .then((certifications) => { - const sortedCertifications: Array = sortCertifications( - certifications, - sort.current.field, - sort.current.direction, - ) + const filteredCertifications: Array = getFilteredCertifications(certifications) + const sortedCertifications: Array = getSortedCertifications(filteredCertifications) setState((prevState) => ({ ...prevState, diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts index 1fb9b9c4a..f8e61f839 100755 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts @@ -1,4 +1,3 @@ export * from './all-certifications-functions' export * from './all-certifications-provider-data.model' -export * from './all-certifications-sort-options' export * from './all-certifications.provider' diff --git a/src-ts/tools/learn/welcome/WelcomePage.tsx b/src-ts/tools/learn/welcome/WelcomePage.tsx index c19cd12ee..2d4542181 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.tsx +++ b/src-ts/tools/learn/welcome/WelcomePage.tsx @@ -1,13 +1,12 @@ import classNames from 'classnames' -import { Dispatch, FC, SetStateAction } from 'react' +import { uniq } from 'lodash' +import { Dispatch, FC, SetStateAction, useMemo } from 'react' import { ContentLayout, InputSelect, LoadingSpinner, Portal, useLocalStorage } from '../../../lib' import '../../../lib/styles/index.scss' import { AllCertificationsProviderData, - ALL_CERTIFICATIONS_DEFAULT_SORT, - ALL_CERTIFICATIONS_SORT_FIELD_TYPE, - ALL_CERTIFICATIONS_SORT_OPTIONS, + LearnCertification, useAllCertifications, UserCertificationsProviderData, useUserCertifications, @@ -18,17 +17,34 @@ import { CoursesCard } from './courses-card' import { ProgressBlock } from './progress-block' import styles from './WelcomePage.module.scss' +type SORT_FIELD_TYPE = keyof LearnCertification +const SORT_OPTIONS: Array<{label: string, value: SORT_FIELD_TYPE}> = [ + {label: 'Category', value: 'category'}, + {label: 'Newest', value: 'createdAt'}, + {label: 'Title', value: 'title'}, +] +export const DEFAULT_SORT: SORT_FIELD_TYPE = SORT_OPTIONS[0].value + const WelcomePage: FC<{}> = () => { const [sortField, setSortField]: [ - ALL_CERTIFICATIONS_SORT_FIELD_TYPE, - Dispatch> - ] = useLocalStorage('tca-welcome-sort-certs', ALL_CERTIFICATIONS_DEFAULT_SORT) + SORT_FIELD_TYPE, + Dispatch> + ] = useLocalStorage('tca-welcome-sort-certs', DEFAULT_SORT) + + const [selectedCategory, setSelectedCategory]: [ + string, + Dispatch> + ] = useLocalStorage('tca-welcome-filter-certs', '') const allCertsData: AllCertificationsProviderData = useAllCertifications( undefined, undefined, { + filter: { + field: 'category', + value: selectedCategory, + }, sort: { direction: sortField === 'createdAt' ? 'desc' : 'asc', field: sortField, @@ -39,6 +55,14 @@ const WelcomePage: FC<{}> = () => { const coursesReady: boolean = allCertsData.ready && userCertsData.ready + const certsCategoriesOptions: Array<{label: string, value: string}> = useMemo(() => { + const certsCategories: Array = uniq(allCertsData.allCertifications.map(c => c.category)) + return [ + {label: 'All Categories', value: ''}, + ...certsCategories.map((c) => ({value: c, label: c})), + ] + }, [allCertsData]) + return ( @@ -80,9 +104,16 @@ const WelcomePage: FC<{}> = () => {
    setSelectedCategory(e.target.value as string)} + name='filter-courses' + label='Categories' + > + setSortField(e.target.value as ALL_CERTIFICATIONS_SORT_FIELD_TYPE)} + onChange={(e) => setSortField(e.target.value as SORT_FIELD_TYPE)} name='sort-courses' label='Sort by' > From 9f833123e2f01640c83f5a208e72220b0f6a5de4 Mon Sep 17 00:00:00 2001 From: Brooke Date: Mon, 19 Sep 2022 15:53:46 -0700 Subject: [PATCH 057/113] GAME-107 #comment This commit refactors the SWR to be a generic hook that lives in the lib and can be re-used elsewhere. It makes the styling of the Load More button static. It moves everything to do w/routing to the .routes page and removes duplicate paths. It creates a breadccrumb provider for the Gamification tool so we're not duplicating the base item. It moves the retrieval of the badge info into a provider so that it's not directly in the presentation layer. #time 4h --- src-ts/lib/pagination/index.ts | 3 + .../lib/pagination/infinite-page-dao.model.ts | 4 + .../pagination/infinite-page-handler.model.ts | 5 + .../lib/pagination/use-infinite-page.hook.ts | 25 ++++ src-ts/lib/table/Table.tsx | 37 +++--- src-ts/lib/table/index.ts | 1 + src-ts/lib/table/table-column.model.ts | 1 - .../table/table-functions/table.functions.ts | 18 +-- .../gamification-config.model.ts | 1 + .../gamification.config.ts | 0 .../gamification.default.config.ts | 1 + .../gamification.dev.config.ts | 3 +- .../gamification.prod.config.ts | 2 + .../{config => game-config}/index.ts | 0 .../game-badge.model.ts} | 3 +- .../gamification-admin/game-lib/index.ts | 3 + .../game-lib/use-get-game-badges-page.hook.ts | 30 +++++ .../useGamificationBreadcrumb.provider.tsx | 16 +++ .../gamification-admin.routes.tsx | 22 +++- .../lib/hooks/getDataSource.ts | 5 - .../pages/badge-detail/BadgeDetailPage.tsx | 44 +++---- .../pages/badge-listing/BadgeListingPage.tsx | 109 ++++++++---------- .../BadgeActionRenderer.tsx | 54 +++++++-- .../badge-listing-table.config.tsx | 6 +- .../BadgeListingNameRenderer.tsx | 10 +- .../badge-listing-table/index.ts | 1 + .../pages/create-badge/CreateBadgePage.tsx | 40 ++++--- 27 files changed, 287 insertions(+), 157 deletions(-) create mode 100644 src-ts/lib/pagination/infinite-page-dao.model.ts create mode 100644 src-ts/lib/pagination/infinite-page-handler.model.ts create mode 100644 src-ts/lib/pagination/use-infinite-page.hook.ts rename src-ts/tools/gamification-admin/{config => game-config}/gamification-config.model.ts (75%) rename src-ts/tools/gamification-admin/{config => game-config}/gamification.config.ts (100%) rename src-ts/tools/gamification-admin/{config => game-config}/gamification.default.config.ts (91%) rename src-ts/tools/gamification-admin/{config => game-config}/gamification.dev.config.ts (56%) rename src-ts/tools/gamification-admin/{config => game-config}/gamification.prod.config.ts (63%) rename src-ts/tools/gamification-admin/{config => game-config}/index.ts (100%) rename src-ts/tools/gamification-admin/{lib/models/badge.model.ts => game-lib/game-badge.model.ts} (60%) create mode 100644 src-ts/tools/gamification-admin/game-lib/index.ts create mode 100644 src-ts/tools/gamification-admin/game-lib/use-get-game-badges-page.hook.ts create mode 100644 src-ts/tools/gamification-admin/game-lib/useGamificationBreadcrumb.provider.tsx delete mode 100644 src-ts/tools/gamification-admin/lib/hooks/getDataSource.ts create mode 100644 src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/index.ts diff --git a/src-ts/lib/pagination/index.ts b/src-ts/lib/pagination/index.ts index 3b90b0623..b8e69f71a 100644 --- a/src-ts/lib/pagination/index.ts +++ b/src-ts/lib/pagination/index.ts @@ -1,2 +1,5 @@ +export * from './infinite-page-dao.model' +export * from './infinite-page-handler.model' export * from './page.model' export * from './sort.model' +export * from './use-infinite-page.hook' diff --git a/src-ts/lib/pagination/infinite-page-dao.model.ts b/src-ts/lib/pagination/infinite-page-dao.model.ts new file mode 100644 index 000000000..2b2520095 --- /dev/null +++ b/src-ts/lib/pagination/infinite-page-dao.model.ts @@ -0,0 +1,4 @@ +export interface InfinitePageDao { + count: number + rows: ReadonlyArray +} diff --git a/src-ts/lib/pagination/infinite-page-handler.model.ts b/src-ts/lib/pagination/infinite-page-handler.model.ts new file mode 100644 index 000000000..cab09cc1a --- /dev/null +++ b/src-ts/lib/pagination/infinite-page-handler.model.ts @@ -0,0 +1,5 @@ +export interface InfinitePageHandler { + data?: ReadonlyArray + getAndSetNext: () => void + hasMore: boolean +} diff --git a/src-ts/lib/pagination/use-infinite-page.hook.ts b/src-ts/lib/pagination/use-infinite-page.hook.ts new file mode 100644 index 000000000..6f1367ea2 --- /dev/null +++ b/src-ts/lib/pagination/use-infinite-page.hook.ts @@ -0,0 +1,25 @@ +import { flatten, map } from 'lodash' +// tslint:disable-next-line: no-submodule-imports +import useSWRInfinite, { SWRInfiniteResponse } from 'swr/infinite' + +import { InfinitePageDao } from './infinite-page-dao.model' +import { InfinitePageHandler } from './infinite-page-handler.model' + +export function useGetInfinitePage(getKey: (index: number, previousPageData: InfinitePageDao) => string | undefined): + InfinitePageHandler { + + const { data, setSize, size }: SWRInfiniteResponse> = useSWRInfinite(getKey, { revalidateFirstPage: false }) + + // flatten version of badges paginated data + const outputData: ReadonlyArray = flatten(map(data, dao => dao.rows)) + + function getAndSetNext(): void { + setSize(size + 1) + } + + return { + data: outputData, + getAndSetNext, + hasMore: outputData.length < (data?.[0]?.count || 0), + } +} diff --git a/src-ts/lib/table/Table.tsx b/src-ts/lib/table/Table.tsx index 7858bb476..ad3443d25 100644 --- a/src-ts/lib/table/Table.tsx +++ b/src-ts/lib/table/Table.tsx @@ -1,7 +1,7 @@ import classNames from 'classnames' import { Dispatch, MouseEvent, SetStateAction, useEffect, useState } from 'react' -import { Button, ButtonSize, ButtonStyle } from '../button' +import { Button } from '../button' import { Sort } from '../pagination' import '../styles/_includes.scss' import { IconOutline } from '../svgs' @@ -16,13 +16,10 @@ import styles from './Table.module.scss' interface TableProps { readonly columns: ReadonlyArray> readonly data: ReadonlyArray - readonly loadMoreBtnLabel?: string - readonly loadMoreBtnSize?: ButtonSize - readonly loadMoreBtnStyle?: ButtonStyle readonly moreToLoad?: boolean - readonly onLoadMoreClick?: (data: T) => void + readonly onLoadMoreClick?: () => void readonly onRowClick?: (data: T) => void - readonly onToggleSort?: (sort: Sort | undefined) => void + readonly onToggleSort?: (sort: Sort) => void } interface DefaultSortDirectionMap { @@ -41,7 +38,7 @@ const Table: (props: TableProps) = Dispatch> ] = useState() - const [sortedData, setSortData]: [ReadonlyArray, Dispatch>>] + const [sortedData, setSortedData]: [ReadonlyArray, Dispatch>>] = useState>(props.data) useEffect(() => { @@ -54,7 +51,11 @@ const Table: (props: TableProps) = setDefaultSortDirectionMap(map) } - setSortData(tableGetSorted(data, columns, sort)) + // if we have a sort handler, don't worry about getting the sorted data; + // otherwise, get the sorted data for the table + const sorted: ReadonlyArray = !!props.onToggleSort ? data : tableGetSorted(data, columns, sort) + + setSortedData(sorted) }, [ columns, @@ -84,9 +85,7 @@ const Table: (props: TableProps) = setSort(newSort) // call the callback to notify parent for sort update - if (props.onToggleSort) { - props.onToggleSort(newSort) - } + props.onToggleSort?.(newSort) } const headerRow: Array = props.columns @@ -95,13 +94,12 @@ const Table: (props: TableProps) = const isCurrentlySorted: boolean = isSortable && col.propertyName === sort?.fieldName const colorClass: string = isCurrentlySorted ? 'black-100' : 'black-60' const sortableClass: string | undefined = isSortable ? styles.sortable : undefined - const centerClass: string | undefined = col.centerHeader ? styles.centerHeader : undefined return (
    -
    +
    {col.label} {!!col.tooltip && (
    @@ -172,9 +170,16 @@ const Table: (props: TableProps) =
    { - props.moreToLoad &&
    -
    + !!props.moreToLoad && !!props.onLoadMoreClick && ( +
    +
    + ) }
    ) diff --git a/src-ts/lib/table/index.ts b/src-ts/lib/table/index.ts index d7b124c44..396d852ea 100644 --- a/src-ts/lib/table/index.ts +++ b/src-ts/lib/table/index.ts @@ -1,2 +1,3 @@ export * from './table-column.model' +export { tableGetDefaultSort } from './table-functions' export { default as Table } from './Table' diff --git a/src-ts/lib/table/table-column.model.ts b/src-ts/lib/table/table-column.model.ts index 6d7ddb0c9..4323da523 100644 --- a/src-ts/lib/table/table-column.model.ts +++ b/src-ts/lib/table/table-column.model.ts @@ -1,7 +1,6 @@ import { TableCellType } from './table-cell.type' export interface TableColumn { - readonly centerHeader?: boolean readonly defaultSortDirection?: 'asc' | 'desc' readonly isDefaultSort?: boolean readonly label?: string diff --git a/src-ts/lib/table/table-functions/table.functions.ts b/src-ts/lib/table/table-functions/table.functions.ts index cfd4d25af..72e050f05 100644 --- a/src-ts/lib/table/table-functions/table.functions.ts +++ b/src-ts/lib/table/table-functions/table.functions.ts @@ -1,17 +1,21 @@ import { Sort } from '../../pagination' import { TableColumn } from '../table-column.model' -export function getDefaultSort(columns: ReadonlyArray>): Sort | undefined { +export function getDefaultSort(columns: ReadonlyArray>): Sort { const defaultSortColumn: TableColumn | undefined = columns.find(col => col.isDefaultSort) || columns.find(col => !!col.propertyName) + || columns?.[0] - const defaultSort: Sort | undefined = !defaultSortColumn?.propertyName - ? undefined - : { - direction: defaultSortColumn.defaultSortDirection || 'asc', - fieldName: defaultSortColumn.propertyName, - } + // if we didn't find a default sort, we have a problem + if (!defaultSortColumn) { + throw new Error('A table must have at least one column.') + } + + const defaultSort: Sort = { + direction: defaultSortColumn.defaultSortDirection || 'asc', + fieldName: defaultSortColumn.propertyName || '', + } return defaultSort } diff --git a/src-ts/tools/gamification-admin/config/gamification-config.model.ts b/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts similarity index 75% rename from src-ts/tools/gamification-admin/config/gamification-config.model.ts rename to src-ts/tools/gamification-admin/game-config/gamification-config.model.ts index f090f1389..c1b0124fe 100644 --- a/src-ts/tools/gamification-admin/config/gamification-config.model.ts +++ b/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts @@ -1,3 +1,4 @@ export interface GamificationConfigModel { ORG_ID: string + PAGE_SIZE: number } diff --git a/src-ts/tools/gamification-admin/config/gamification.config.ts b/src-ts/tools/gamification-admin/game-config/gamification.config.ts similarity index 100% rename from src-ts/tools/gamification-admin/config/gamification.config.ts rename to src-ts/tools/gamification-admin/game-config/gamification.config.ts diff --git a/src-ts/tools/gamification-admin/config/gamification.default.config.ts b/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts similarity index 91% rename from src-ts/tools/gamification-admin/config/gamification.default.config.ts rename to src-ts/tools/gamification-admin/game-config/gamification.default.config.ts index aa7ad4626..b2c40e4d8 100644 --- a/src-ts/tools/gamification-admin/config/gamification.default.config.ts +++ b/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts @@ -2,4 +2,5 @@ import { GamificationConfigModel } from './gamification-config.model' export const GamificationConfigDefault: GamificationConfigModel = { ORG_ID: '6052dd9b-ea80-494b-b258-edd1331e27a3', + PAGE_SIZE: 12, } diff --git a/src-ts/tools/gamification-admin/config/gamification.dev.config.ts b/src-ts/tools/gamification-admin/game-config/gamification.dev.config.ts similarity index 56% rename from src-ts/tools/gamification-admin/config/gamification.dev.config.ts rename to src-ts/tools/gamification-admin/game-config/gamification.dev.config.ts index 63bd27ddf..02fec4630 100644 --- a/src-ts/tools/gamification-admin/config/gamification.dev.config.ts +++ b/src-ts/tools/gamification-admin/game-config/gamification.dev.config.ts @@ -1,5 +1,6 @@ import { GamificationConfigModel } from './gamification-config.model' +import { GamificationConfigDefault } from './gamification.default.config' export const GamificationConfigDev: GamificationConfigModel = { - ORG_ID: '6052dd9b-ea80-494b-b258-edd1331e27a3', + ...GamificationConfigDefault, } diff --git a/src-ts/tools/gamification-admin/config/gamification.prod.config.ts b/src-ts/tools/gamification-admin/game-config/gamification.prod.config.ts similarity index 63% rename from src-ts/tools/gamification-admin/config/gamification.prod.config.ts rename to src-ts/tools/gamification-admin/game-config/gamification.prod.config.ts index 922edc652..e49185be0 100644 --- a/src-ts/tools/gamification-admin/config/gamification.prod.config.ts +++ b/src-ts/tools/gamification-admin/game-config/gamification.prod.config.ts @@ -1,5 +1,7 @@ import { GamificationConfigModel } from './gamification-config.model' +import { GamificationConfigDefault } from './gamification.default.config' export const GamificationConfigProd: GamificationConfigModel = { + ...GamificationConfigDefault, ORG_ID: 'e111f8df-6ac8-44d1-b4da-bb916f5e3425', } diff --git a/src-ts/tools/gamification-admin/config/index.ts b/src-ts/tools/gamification-admin/game-config/index.ts similarity index 100% rename from src-ts/tools/gamification-admin/config/index.ts rename to src-ts/tools/gamification-admin/game-config/index.ts diff --git a/src-ts/tools/gamification-admin/lib/models/badge.model.ts b/src-ts/tools/gamification-admin/game-lib/game-badge.model.ts similarity index 60% rename from src-ts/tools/gamification-admin/lib/models/badge.model.ts rename to src-ts/tools/gamification-admin/game-lib/game-badge.model.ts index c88efeb78..7c4c719b2 100644 --- a/src-ts/tools/gamification-admin/lib/models/badge.model.ts +++ b/src-ts/tools/gamification-admin/game-lib/game-badge.model.ts @@ -1,4 +1,5 @@ -export interface Badge { +// TODO: add factory to convert snake case property names to camel case +export interface GameBadge { active: boolean badge_description: string badge_image_url: string diff --git a/src-ts/tools/gamification-admin/game-lib/index.ts b/src-ts/tools/gamification-admin/game-lib/index.ts new file mode 100644 index 000000000..f9755858a --- /dev/null +++ b/src-ts/tools/gamification-admin/game-lib/index.ts @@ -0,0 +1,3 @@ +export * from './game-badge.model' +export * from './use-get-game-badges-page.hook' +export * from './game-badge.model' diff --git a/src-ts/tools/gamification-admin/game-lib/use-get-game-badges-page.hook.ts b/src-ts/tools/gamification-admin/game-lib/use-get-game-badges-page.hook.ts new file mode 100644 index 000000000..885d1af51 --- /dev/null +++ b/src-ts/tools/gamification-admin/game-lib/use-get-game-badges-page.hook.ts @@ -0,0 +1,30 @@ +import { EnvironmentConfig } from '../../../config' +import { InfinitePageDao, InfinitePageHandler, Sort, useGetInfinitePage } from '../../../lib' +import { GamificationConfig } from '../game-config' + +import { GameBadge } from './game-badge.model' + +export function useGetGameBadgesPage(sort: Sort): InfinitePageHandler { + + function getKey(index: number, previousPageData: InfinitePageDao): string | undefined { + + // reached the end + if (!!previousPageData && !previousPageData.rows.length) { + return undefined + } + + const params: Record = { + limit: `${GamificationConfig.PAGE_SIZE}`, + offset: `${index * GamificationConfig.PAGE_SIZE}`, + order_by: sort.fieldName, + order_type: sort.direction, + organization_id: GamificationConfig.ORG_ID, + } + + const badgeEndpointUrl: URL = new URL(`${EnvironmentConfig.API.V5}/gamification/badges?${new URLSearchParams(params)}`) + + return badgeEndpointUrl.toString() + } + + return useGetInfinitePage(getKey) +} diff --git a/src-ts/tools/gamification-admin/game-lib/useGamificationBreadcrumb.provider.tsx b/src-ts/tools/gamification-admin/game-lib/useGamificationBreadcrumb.provider.tsx new file mode 100644 index 000000000..274022b16 --- /dev/null +++ b/src-ts/tools/gamification-admin/game-lib/useGamificationBreadcrumb.provider.tsx @@ -0,0 +1,16 @@ +import { BreadcrumbItemModel } from '../../../lib' +import { basePath } from '../gamification-admin.routes' +import { toolTitle } from '../GamificationAdmin' + +export function useGamificationBreadcrumb(items: Array): Array { + + const breadcrumb: Array = [ + { + name: toolTitle, + url: basePath, + }, + ...items, + ] + + return breadcrumb +} diff --git a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx index 24ee0b8c1..108d2cf53 100644 --- a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx +++ b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx @@ -5,8 +5,16 @@ import BadgeDetailPage from './pages/badge-detail/BadgeDetailPage' import BadgeListingPage from './pages/badge-listing/BadgeListingPage' import CreateBadgePage from './pages/create-badge/CreateBadgePage' -export const baseUrl: string = '/gamification-admin' -export const rolesRequired: Array = [UserRole.gamificationAdmin] +const baseDetailPath: string = '/badge-detail' +const createBadgePath: string = '/create-badge' + +export const basePath: string = '/gamification-admin' + +export function badgeDetailPath(badgeId: string, view?: 'edit' | 'award'): string { + return `${basePath}${baseDetailPath}/${badgeId}${!!view ? `#${view}` : ''}` +} + +export const createBadgeRoute: string = `${basePath}${createBadgePath}` export const gamificationAdminRoutes: Array = [ { @@ -18,17 +26,19 @@ export const gamificationAdminRoutes: Array = [ }, { element: , - route: '/create-badge', + route: createBadgePath, }, { element: , - route: '/badge-detail/:id', + route: `${baseDetailPath}/:id`, }, ], element: , hidden: true, - rolesRequired, - route: baseUrl, + rolesRequired: [ + UserRole.gamificationAdmin, + ], + route: basePath, title: toolTitle, }, ] diff --git a/src-ts/tools/gamification-admin/lib/hooks/getDataSource.ts b/src-ts/tools/gamification-admin/lib/hooks/getDataSource.ts deleted file mode 100644 index d81ffdcab..000000000 --- a/src-ts/tools/gamification-admin/lib/hooks/getDataSource.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { EnvironmentConfig } from '../../../../config' - -export default function getDataSource(): string { - return `${EnvironmentConfig.API.V5}/gamification` -} diff --git a/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx b/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx index 1e84dc1ed..5283989c4 100644 --- a/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-detail/BadgeDetailPage.tsx @@ -1,32 +1,34 @@ -import { FC, useMemo } from 'react' +import { FC } from 'react' import { Breadcrumb, BreadcrumbItemModel, ContentLayout } from '../../../../lib' -import { baseUrl } from '../../gamification-admin.routes' -import { toolTitle } from '../../GamificationAdmin' +import { useGamificationBreadcrumb } from '../../game-lib' import styles from './BadgeDetailPage.module.scss' const BadgeDetailPage: FC = () => { - // TDOD: use whit GAME-78 - // const { id: badgeID } : { badgeID: string } = useParams() - const breadcrumb: Array = useMemo(() => [ - { name: toolTitle, url: baseUrl }, - { name: 'badge detail', url: '#' }, - ], []) + // TDOD: use whit GAME-78 + // const { id: badgeID } : { badgeID: string } = useParams() - return ( - - -
    + const breadcrumb: Array = useGamificationBreadcrumb([ + { + name: 'badge detail', + url: '#', + }, + ]) -
    -
    - ) + return ( + + +
    + +
    +
    + ) } export default BadgeDetailPage diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx index 08dc7c2be..1e192bd60 100644 --- a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx @@ -1,77 +1,64 @@ -import { flatten, map } from 'lodash' import { Dispatch, FC, SetStateAction, useState } from 'react' import { NavigateFunction, useNavigate } from 'react-router-dom' -// tslint:disable-next-line -import useSWRInfinite from 'swr/infinite' -import { ButtonProps, ContentLayout, LoadingSpinner, Sort, Table, TableColumn } from '../../../../lib' -import { GamificationConfig } from '../../config' -import { baseUrl } from '../../gamification-admin.routes' -import getDataSource from '../../lib/hooks/getDataSource' -import { Badge } from '../../lib/models/badge.model' +import { + ButtonProps, + ContentLayout, + InfinitePageHandler, + LoadingSpinner, + Sort, + Table, + TableColumn, + tableGetDefaultSort +} from '../../../../lib' +import { GameBadge, useGetGameBadgesPage } from '../../game-lib' +import { createBadgeRoute } from '../../gamification-admin.routes' -import { badgeListingColumns } from './badge-listing-table/badge-listing-table.config' +import { badgeListingColumns } from './badge-listing-table' import styles from './BadgeListingPage.module.scss' const BadgeListingPage: FC = () => { - const [order, setOrder]: any = useState({ by: 'badge_name', type: 'asc' }) - const navigate: NavigateFunction = useNavigate() - const dataSource: string = getDataSource() - // server-side pagination hook - const getKey: any = (pageIndex: any, previousPageData: any) => { - if (previousPageData && !previousPageData.rows.length) { return undefined } // reached the end - return `${dataSource}/badges?organization_id=${GamificationConfig.ORG_ID}&limit=12&offset=${pageIndex * 12}&order_by=${order.by}&order_type=${order.type}` - } - const { data: badges, size, setSize }: any = useSWRInfinite(getKey, { revalidateFirstPage: false }) + const [sort, setSort]: [Sort, Dispatch>] = useState(tableGetDefaultSort(badgeListingColumns)) + const [columns]: [ + ReadonlyArray>, + Dispatch>>>, + ] + = useState>>([...badgeListingColumns]) - const tableData: Array = flatten(map(badges, page => page.rows)) // flatten version of badges paginated data - const loadedCnt: any = badges?.reduce((ps: any, a: any) => ps + a.rows.length, 0) // how much data is loaded so far + const gameBadgeDataHandler: InfinitePageHandler = useGetGameBadgesPage(sort) + const navigate: NavigateFunction = useNavigate() - // listing table config - const [columns]: [ - ReadonlyArray>, - Dispatch>>>, - ] - = useState>>([...badgeListingColumns]) + function onSortClick(newSort: Sort): void { + setSort({ ...newSort }) + } - // on sort toggle callback - const onOrderClick: any = (sort: Sort) => { - setOrder({ - by: sort.fieldName, - type: sort.direction, - }) - } + // header button config + const buttonConfig: ButtonProps = { + label: 'Create New Badge', + onClick: () => navigate(createBadgeRoute), + } - // on load more callback - const onLoadMoreClick: any = () => setSize(size + 1) - // header button config - const buttonConfig: ButtonProps = { - label: 'Create New Badge', - onClick: () => navigate(`${baseUrl}/create-badge`), - } + if (!gameBadgeDataHandler.data) { + return + } - if (!badges) { return } - - return ( - -
    - - - - ) + return ( + +
    +
    + + + ) } export default BadgeListingPage diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.tsx index 33972e587..6ca456669 100644 --- a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-action-renderer/BadgeActionRenderer.tsx @@ -1,19 +1,49 @@ -import { Button, useCheckIsMobile } from '../../../../../../lib' -import { baseUrl } from '../../../../gamification-admin.routes' -import { Badge } from '../../../../lib/models/badge.model' +import { Button, ButtonProps, useCheckIsMobile } from '../../../../../../lib' +import { GameBadge } from '../../../../game-lib' +import { badgeDetailPath } from '../../../../gamification-admin.routes' import styles from './BadgeActionRenderer.module.scss' -function BadgeActionRenderer(badge: Badge): JSX.Element { - const isMobile: boolean = useCheckIsMobile() +function BadgeActionRenderer(badge: GameBadge): JSX.Element { - return ( -
    -
    - ) + const isMobile: boolean = useCheckIsMobile() + + const buttonProps: ButtonProps = { + buttonStyle: 'secondary', + size: isMobile ? 'xs' : 'sm', + } + + const actionButtons: Array<{ + label: string + view?: 'edit' | 'award' + }> = [ + { + label: 'View', + }, + { + label: 'Edit', + view: 'edit', + }, + { + label: 'Award', + view: 'award', + }, + ] + + return ( +
    + {actionButtons.map((button, index) => { + return ( +
    + ) } export default BadgeActionRenderer diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-listing-table.config.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-listing-table.config.tsx index ab9667ebf..1d6d54ef4 100644 --- a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-listing-table.config.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-listing-table.config.tsx @@ -1,10 +1,10 @@ import { TableColumn } from '../../../../../lib' -import { Badge } from '../../../lib/models/badge.model' +import { GameBadge } from '../../../game-lib' import { BadgeActionRenderer } from './badge-action-renderer' import { BadgeListingNameRenderer } from './badge-name-renderer' -export const badgeListingColumns: ReadonlyArray> = [ +export const badgeListingColumns: ReadonlyArray> = [ { defaultSortDirection: 'asc', isDefaultSort: true, @@ -14,8 +14,6 @@ export const badgeListingColumns: ReadonlyArray> = [ type: 'element', }, { - centerHeader: true, - label: 'Actions', renderer: BadgeActionRenderer, type: 'action', }, diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.tsx index 333aed2e7..7b9c604ca 100644 --- a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/badge-name-renderer/BadgeListingNameRenderer.tsx @@ -1,11 +1,15 @@ -import { Badge } from '../../../../lib/models/badge.model' +import { GameBadge } from '../../../../game-lib' import styles from './BadgeListingNameRenderer.module.scss' -function BadgeListingNameRenderer(badge: Badge): JSX.Element { +function BadgeListingNameRenderer(badge: GameBadge): JSX.Element { return (
    - {badge.badge_name} + {badge.badge_name}

    {badge.badge_name}

    ) diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/index.ts b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/index.ts new file mode 100644 index 000000000..8fa32ec69 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/badge-listing/badge-listing-table/index.ts @@ -0,0 +1 @@ +export * from './badge-listing-table.config' diff --git a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx index 020b7f8eb..22d7e59d6 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx @@ -1,30 +1,32 @@ -import { FC, useMemo } from 'react' +import { FC } from 'react' import { Breadcrumb, BreadcrumbItemModel, ContentLayout } from '../../../../lib' -import { baseUrl } from '../../gamification-admin.routes' -import { toolTitle } from '../../GamificationAdmin' +import { useGamificationBreadcrumb } from '../../game-lib' import styles from './CreateBadgePage.module.scss' const CreateBadgePage: FC = () => { - const breadcrumb: Array = useMemo(() => [ - { name: toolTitle, url: baseUrl }, - { name: 'create badge', url: '#' }, - ], []) - return ( - - -
    + const breadcrumb: Array = useGamificationBreadcrumb([ + { + name: 'create badge', + url: '#', + }, + ]) -
    -
    - ) + return ( + + +
    + +
    +
    + ) } export default CreateBadgePage From 95cefd191d7fd777a194ed0c27124e7d8a2b439e Mon Sep 17 00:00:00 2001 From: Brooke Date: Mon, 19 Sep 2022 15:55:06 -0700 Subject: [PATCH 058/113] GAME-107 clean-up --- src-ts/tools/gamification-admin/game-lib/index.ts | 2 +- ...dcrumb.provider.tsx => use-gamification-breadcrumb.hook.tsx} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src-ts/tools/gamification-admin/game-lib/{useGamificationBreadcrumb.provider.tsx => use-gamification-breadcrumb.hook.tsx} (100%) diff --git a/src-ts/tools/gamification-admin/game-lib/index.ts b/src-ts/tools/gamification-admin/game-lib/index.ts index f9755858a..6cb57fe53 100644 --- a/src-ts/tools/gamification-admin/game-lib/index.ts +++ b/src-ts/tools/gamification-admin/game-lib/index.ts @@ -1,3 +1,3 @@ export * from './game-badge.model' export * from './use-get-game-badges-page.hook' -export * from './game-badge.model' +export * from './use-gamification-breadcrumb.hook' diff --git a/src-ts/tools/gamification-admin/game-lib/useGamificationBreadcrumb.provider.tsx b/src-ts/tools/gamification-admin/game-lib/use-gamification-breadcrumb.hook.tsx similarity index 100% rename from src-ts/tools/gamification-admin/game-lib/useGamificationBreadcrumb.provider.tsx rename to src-ts/tools/gamification-admin/game-lib/use-gamification-breadcrumb.hook.tsx From 5ac9f54ee24956ee4db9aabb1da7de41e29ce90b Mon Sep 17 00:00:00 2001 From: Brooke Date: Mon, 19 Sep 2022 16:09:52 -0700 Subject: [PATCH 059/113] GAMe-107 clean up --- src-ts/lib/pagination/infinite-page-dao.model.ts | 1 + .../pages/badge-listing/BadgeListingPage.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src-ts/lib/pagination/infinite-page-dao.model.ts b/src-ts/lib/pagination/infinite-page-dao.model.ts index 2b2520095..ae1ca01da 100644 --- a/src-ts/lib/pagination/infinite-page-dao.model.ts +++ b/src-ts/lib/pagination/infinite-page-dao.model.ts @@ -1,4 +1,5 @@ export interface InfinitePageDao { count: number + // TODO: rename this 'items' so it can be used in a grid/card view rows: ReadonlyArray } diff --git a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx index 1e192bd60..dfa788084 100644 --- a/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx +++ b/src-ts/tools/gamification-admin/pages/badge-listing/BadgeListingPage.tsx @@ -26,7 +26,7 @@ const BadgeListingPage: FC = () => { ] = useState>>([...badgeListingColumns]) - const gameBadgeDataHandler: InfinitePageHandler = useGetGameBadgesPage(sort) + const pageHandler: InfinitePageHandler = useGetGameBadgesPage(sort) const navigate: NavigateFunction = useNavigate() function onSortClick(newSort: Sort): void { @@ -39,7 +39,7 @@ const BadgeListingPage: FC = () => { onClick: () => navigate(createBadgeRoute), } - if (!gameBadgeDataHandler.data) { + if (!pageHandler.data) { return } @@ -51,9 +51,9 @@ const BadgeListingPage: FC = () => {
    From 186fe43be839a2a68ffdcc374e31bb923e5d4162 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 20 Sep 2022 11:54:15 +0300 Subject: [PATCH 060/113] TCA-441 - learn landing filters - mobile UI --- .../input-select/InputSelect.module.scss | 12 ++++- .../form-input/input-select/InputSelect.tsx | 54 +++++++++---------- .../form-input/input-wrapper/InputWrapper.tsx | 3 +- .../learn/welcome/WelcomePage.module.scss | 24 +++++++++ 4 files changed, 64 insertions(+), 29 deletions(-) diff --git a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss index bf55e8a28..7d4c73668 100644 --- a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss +++ b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss @@ -17,9 +17,19 @@ } } -.select-menu { +.menu-wrap { position: absolute; top: calc(100% - 2px); + left: 0; + width: 100%; + &:not(:empty) { + z-index: 9; + } +} + +.select-menu { + position: absolute; + top: 100%; left: -1px; right: -1px; background: $tc-white; diff --git a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx index 602f09e48..f9da6acf8 100644 --- a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx @@ -68,34 +68,34 @@ const InputSelect: FC = (props: InputSelectProps) => { hideInlineErrors={props.hideInlineErrors} ref={triggerRef} > - {selectedOption && ( -
    - {label(selectedOption)} - - - -
    - )} +
    + {selectedOption ? label(selectedOption) : ''} + + + +
    - {menuIsVisible && ( -
    - {props.options.map((option) => ( -
    - {label(option)} -
    - ))} -
    - )} +
    + {menuIsVisible && ( +
    + {props.options.map((option) => ( +
    + {label(option)} +
    + ))} +
    + )} +
    ) diff --git a/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx b/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx index b51d6f32e..f3468cb49 100644 --- a/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx @@ -28,6 +28,7 @@ const InputWrapper: ForwardRefExoticComponent = forwardRef = forwardRef diff --git a/src-ts/tools/learn/welcome/WelcomePage.module.scss b/src-ts/tools/learn/welcome/WelcomePage.module.scss index 97ee4655c..deaf82325 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.module.scss +++ b/src-ts/tools/learn/welcome/WelcomePage.module.scss @@ -57,6 +57,12 @@ align-items: center; gap: $space-sm; } + + @include ltemd { + flex-direction: column; + align-items: flex-start; + gap: $space-xxl; + } } .badge { @@ -77,4 +83,22 @@ > * { min-width: 326px; } + + > :global(.input-wrapper) { + width: 100%; + + > :global(.input-el) { + margin: 0; + } + } + + @include ltelg { + flex-direction: column; + align-items: flex-start; + gap: $space-lg; + } + + @include ltemd { + width: 100%; + } } From 8a59c3f82fb9822cb6fcc8d358ff5ca0bbfe3a68 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 20 Sep 2022 12:03:09 +0300 Subject: [PATCH 061/113] TCA-441 - update button style for secondary buttons on learn landing page --- src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx b/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx index 29c461d0a..2ceaabb38 100644 --- a/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx +++ b/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx @@ -1,7 +1,7 @@ import classNames from 'classnames' import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react' -import { Button } from '../../../../lib' +import { Button, ButtonStyle } from '../../../../lib' import { CourseTitle, LearnCertification, @@ -20,6 +20,8 @@ interface CoursesCardProps { const CoursesCard: FC = (props: CoursesCardProps) => { + const [buttonStyle, setButtonStyle]: [ButtonStyle, Dispatch>] + = useState('primary') const [buttonLabel, setButtonLabel]: [string, Dispatch>] = useState('') const [link, setLink]: [string, Dispatch>] @@ -42,6 +44,7 @@ const CoursesCard: FC = (props: CoursesCardProps) => { if (isCompleted) { // if the course is completed, View the Certificate + setButtonStyle('secondary') setButtonLabel('View Certificate') setLink(getCertificatePath( props.certification.providerName, @@ -58,6 +61,7 @@ const CoursesCard: FC = (props: CoursesCardProps) => { } else { // otherwise this course is in-progress, so Resume the course at the next lesson + setButtonStyle('secondary') setButtonLabel('Resume') setLink(getLessonPathFromCurrentLesson( props.certification.providerName, @@ -85,7 +89,7 @@ const CoursesCard: FC = (props: CoursesCardProps) => {
    {!!link && (
    + ) +} + +export default InputImagePicker diff --git a/src-ts/lib/form/form-groups/form-input/input-image-picker/index.ts b/src-ts/lib/form/form-groups/form-input/input-image-picker/index.ts new file mode 100644 index 000000000..ceeb9aa9d --- /dev/null +++ b/src-ts/lib/form/form-groups/form-input/input-image-picker/index.ts @@ -0,0 +1 @@ +export { default as InputImagePicker } from './InputImagePicker' diff --git a/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx b/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx index de7cd32e6..ecbb51d82 100644 --- a/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx @@ -8,7 +8,7 @@ import styles from './InputText.module.scss' export type InputTextTypes = 'checkbox' | 'password' | 'text' -interface InputTextProps { +export interface InputTextProps { readonly autocomplete?: FormInputAutocompleteOption readonly checked?: boolean readonly className?: string diff --git a/src-ts/lib/form/form-input.model.ts b/src-ts/lib/form/form-input.model.ts index 1cb8277f4..991ef7b04 100644 --- a/src-ts/lib/form/form-input.model.ts +++ b/src-ts/lib/form/form-input.model.ts @@ -18,6 +18,7 @@ export interface FormCard { } export interface FormInputModel { + readonly accept?: string readonly autocomplete?: FormInputAutocompleteOption readonly cards?: ReadonlyArray checked?: boolean @@ -27,6 +28,7 @@ export interface FormInputModel { disabled?: boolean error?: string readonly events?: ReadonlyArray + readonly files?: FileList readonly hideInlineErrors?: boolean readonly hint?: string readonly id?: string @@ -36,10 +38,11 @@ export interface FormInputModel { readonly notTabbable?: boolean options?: ReadonlyArray readonly placeholder?: string + readonly size?: number readonly spellCheck?: boolean readonly title?: string touched?: boolean - readonly type: 'card-set' | 'checkbox' | 'password' | 'radio' | 'rating' | 'text' | 'textarea' + readonly type: 'card-set' | 'checkbox' | 'password' | 'radio' | 'rating' | 'text' | 'textarea' | 'image-picker' readonly validators?: ReadonlyArray value?: string | boolean } diff --git a/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts b/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts index c1b0124fe..c397769c1 100644 --- a/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts +++ b/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts @@ -1,4 +1,6 @@ export interface GamificationConfigModel { + ACCEPTED_BADGE_MIME_TYPES: string + MAX_BADGE_IMAGE_FILE_SIZE: number ORG_ID: string PAGE_SIZE: number } diff --git a/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts b/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts index b2c40e4d8..b35ada502 100644 --- a/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts +++ b/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts @@ -1,6 +1,8 @@ import { GamificationConfigModel } from './gamification-config.model' export const GamificationConfigDefault: GamificationConfigModel = { + ACCEPTED_BADGE_MIME_TYPES: 'image/svg+xml,image/svg', + MAX_BADGE_IMAGE_FILE_SIZE: 5000000, // 5mb in bytes ORG_ID: '6052dd9b-ea80-494b-b258-edd1331e27a3', PAGE_SIZE: 12, } diff --git a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss index abde53a3a..fac1cbcbc 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss +++ b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss @@ -1,3 +1,6 @@ +@import "../../../../lib/styles/variables"; + .container { display: flex; + padding-top: $space-xxxxl; } diff --git a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx index b6795c2a6..28a8e2f8b 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx @@ -2,9 +2,9 @@ import { Dispatch, FC, SetStateAction, useState } from 'react' import { Breadcrumb, BreadcrumbItemModel, ContentLayout } from '../../../../lib' import { useGamificationBreadcrumb } from '../../game-lib' -import { CreateBadgeForm, createBadgeFormDef } from './create-badge-form' import { BadgeCreatedModal } from '../../game-lib/modals/badge-created-modal' +import { CreateBadgeForm, createBadgeFormDef } from './create-badge-form' import styles from './CreateBadgePage.module.scss' const CreateBadgePage: FC = () => { diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.module.scss b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.module.scss deleted file mode 100644 index ca132e9b9..000000000 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.module.scss +++ /dev/null @@ -1,2 +0,0 @@ -@import '../../../../../lib/styles/includes'; - diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx index de83013c5..b881b8b06 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx @@ -1,13 +1,10 @@ -import { Dispatch, FC, SetStateAction, useState } from 'react' +import { createRef, Dispatch, FC, RefObject, SetStateAction, useEffect, useState } from 'react' -import { Form, FormDefinition, formGetInputModel, FormInputModel } from '../../../../../lib' +import { Button, Form, FormDefinition, formGetInputModel, FormInputModel, IconOutline } from '../../../../../lib' import { CreateBadgeFormField } from './create-badge-form.config' import { CreateBadgeRequest } from './create-badge-functions' import { createBadgeSubmitRequestAsync } from './create-badge-functions/create-badge-store' -import { createBadgeFormDef } from './create-badge-form.config' - -import styles from './CreateBadgeForm.module.scss' export interface CreateBadgeFormProps { formDef: FormDefinition @@ -15,19 +12,25 @@ export interface CreateBadgeFormProps { } const CreateBadgeForm: FC = (props: CreateBadgeFormProps) => { - - // createBadgeFormDef.buttons.primaryGroup[0].onClick = (e) => { console.log('save btn click', e); e.preventDefault() } - function generateRequest(inputs: ReadonlyArray): CreateBadgeRequest { const badgeName: string = formGetInputModel(inputs, CreateBadgeFormField.badgeName).value as string const badgeDesc: string = formGetInputModel(inputs, CreateBadgeFormField.badgeDesc).value as string - const badgeActive: string = formGetInputModel(inputs, CreateBadgeFormField.badgeActive).value as string - + const badgeActive: boolean = formGetInputModel(inputs, CreateBadgeFormField.badgeActive).value as boolean + const files: FileList = formGetInputModel(inputs, CreateBadgeFormField.badgeActive).files as FileList + + console.log('generateRequest', files) + + if (!files) { + // if we don't have image file we have a problem + throw new Error(`There is no image file selected for the badge`) + } + return { badgeActive, - badgeName, badgeDesc, + badgeName, + file: files[0], } } @@ -41,13 +44,11 @@ const CreateBadgeForm: FC = (props: CreateBadgeFormProps) } return ( - <> - - + ) } diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx index 85dc54eb6..3883ab4e8 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx @@ -1,9 +1,11 @@ -import { FormDefinition, validatorRequired, IconOutline } from '../../../../../lib' +import { FormDefinition, IconOutline, validatorRequired } from '../../../../../lib' +import { GamificationConfig } from '../../../game-config' export enum CreateBadgeFormField { badgeActive = 'badgeActive', badgeName = 'badgeName', badgeDesc = 'badgeDesc', + file = 'file', } export const createBadgeFormDef: FormDefinition = { @@ -21,21 +23,32 @@ export const createBadgeFormDef: FormDefinition = { secondaryGroup: [ { buttonStyle: 'icon-bordered', - size: 'lg', icon: IconOutline.ChevronLeftIcon, - route: '/gamification-admin' - + route: '/gamification-admin', + size: 'lg', }, - ] + ], }, groups: [ { inputs: [ - // { - // label: 'Activate', - // name: CreateBadgeFormField.badgeActive, - // type: 'checkbox', - // }, + { + accept: GamificationConfig.ACCEPTED_BADGE_MIME_TYPES, + name: CreateBadgeFormField.file, + size: GamificationConfig.MAX_BADGE_IMAGE_FILE_SIZE, + type: 'image-picker', + // validators: [ + // { + // validator: validatorRequired, + // }, + // ], + }, + { + checked: true, + label: 'Activate Badge', + name: CreateBadgeFormField.badgeActive, + type: 'checkbox', + }, { label: 'Badge Name', name: CreateBadgeFormField.badgeName, @@ -55,7 +68,7 @@ export const createBadgeFormDef: FormDefinition = { validator: validatorRequired, }, ], - } + }, ], }, ], diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts index 40e648857..26bc449f5 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts @@ -1,5 +1,6 @@ export interface CreateBadgeRequest { - badgeActive: string - badgeName: string + badgeActive: boolean badgeDesc: string + badgeName: string + file: File } From ce758f8f5cbe027500d1157718f0e119e6a0365f Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Thu, 22 Sep 2022 12:13:10 +0300 Subject: [PATCH 068/113] image picker to use value & groups config API --- src-ts/lib/form/form-definition.model.ts | 3 +- .../lib/form/form-functions/form.functions.ts | 16 +++++++--- src-ts/lib/form/form-group.model.ts | 7 ++++ .../form/form-groups/FormGroups.module.scss | 6 ++++ src-ts/lib/form/form-groups/FormGroups.tsx | 17 ++++++++-- .../form-group-item/FormGroupItem.tsx | 12 ++++--- .../InputImagePicker.module.scss | 1 + .../input-image-picker/InputImagePicker.tsx | 10 ++++-- .../form-input/input-text/InputText.tsx | 3 +- src-ts/lib/form/form-input.model.ts | 4 ++- .../validator.functions.ts | 21 ++++++------ .../create-badge-form/CreateBadgeForm.tsx | 13 ++------ .../create-badge-form.config.tsx | 32 ++++++++++++------- 13 files changed, 97 insertions(+), 48 deletions(-) diff --git a/src-ts/lib/form/form-definition.model.ts b/src-ts/lib/form/form-definition.model.ts index fef7d8c58..efd292f8f 100644 --- a/src-ts/lib/form/form-definition.model.ts +++ b/src-ts/lib/form/form-definition.model.ts @@ -1,4 +1,4 @@ -import { FormButton, FormGroup } from '.' +import { FormButton, FormGroup, FormGroupOptions } from '.' export type FormAction = 'save' | 'submit' | undefined @@ -10,6 +10,7 @@ export interface FormButtons { export interface FormDefinition { readonly buttons: FormButtons readonly groups?: Array + readonly groupsOptions?: FormGroupOptions readonly shortName?: string readonly subtitle?: string readonly successMessage?: string diff --git a/src-ts/lib/form/form-functions/form.functions.ts b/src-ts/lib/form/form-functions/form.functions.ts index b2ec0c417..1c4eaf1a3 100644 --- a/src-ts/lib/form/form-functions/form.functions.ts +++ b/src-ts/lib/form/form-functions/form.functions.ts @@ -68,6 +68,7 @@ export async function onSubmitAsync( formValue: T, save: (value: T) => Promise, onSuccess?: () => void, + customValidateForm?: (formElements: HTMLFormControlsCollection, event: ValidationEvent, inputs: ReadonlyArray) => boolean ): Promise { event.preventDefault() @@ -84,7 +85,7 @@ export async function onSubmitAsync( // want to have it look like the submit succeeded const formValues: HTMLFormControlsCollection = (event.target as HTMLFormElement).elements if (action === 'submit') { - const isValid: boolean = validateForm(formValues, action, inputs) + const isValid: boolean = (customValidateForm || validateForm)(formValues, action, inputs) if (!isValid) { return Promise.reject() } @@ -119,6 +120,8 @@ function handleFieldEvent(input: HTMLInputElement | HTMLTextAreaElement, inpu const inputDef: FormInputModel = getInputModel(inputs, input.name) + const inputEl: HTMLInputElement = input as HTMLInputElement + if (event === 'change') { inputDef.dirty = input.value !== originalValue } @@ -126,9 +129,10 @@ function handleFieldEvent(input: HTMLInputElement | HTMLTextAreaElement, inpu // set the def value if (input.type === 'checkbox') { - const checkbox: HTMLInputElement = input as HTMLInputElement - inputDef.value = checkbox.checked - inputDef.checked = checkbox.checked + inputDef.value = inputEl.checked + inputDef.checked = inputEl.checked + } else if (input.type === 'file') { + inputDef.value = inputEl.files || undefined } else { inputDef.value = input.value } @@ -181,7 +185,9 @@ function validateField(formInputDef: FormInputModel, formElements: HTMLFormContr }) } -export function validateForm(formElements: HTMLFormControlsCollection, event: 'blur' | 'change' | 'submit' | 'initial', inputs: ReadonlyArray): boolean { +export type ValidationEvent = 'blur' | 'change' | 'submit' | 'initial' + +export function validateForm(formElements: HTMLFormControlsCollection, event: ValidationEvent, inputs: ReadonlyArray): boolean { const errors: ReadonlyArray = inputs?.filter(formInputDef => { formInputDef.dirty = formInputDef.dirty || event === 'submit' validateField(formInputDef, formElements, event) diff --git a/src-ts/lib/form/form-group.model.ts b/src-ts/lib/form/form-group.model.ts index 7fe97df9f..7f90a5418 100644 --- a/src-ts/lib/form/form-group.model.ts +++ b/src-ts/lib/form/form-group.model.ts @@ -1,3 +1,5 @@ +import { CSSProperties } from 'react' + import { FormInputModel } from './form-input.model' export interface FormGroup { @@ -6,3 +8,8 @@ export interface FormGroup { readonly instructions?: string readonly title?: string } + +export interface FormGroupOptions { + groupWrapStyles?: CSSProperties + renderGroupDividers?: boolean +} diff --git a/src-ts/lib/form/form-groups/FormGroups.module.scss b/src-ts/lib/form/form-groups/FormGroups.module.scss index 6c3e7a4b0..3bf262416 100644 --- a/src-ts/lib/form/form-groups/FormGroups.module.scss +++ b/src-ts/lib/form/form-groups/FormGroups.module.scss @@ -1,5 +1,11 @@ +@import "../../styles/includes"; + .form-groups { display: grid; grid-template-columns: 1fr; justify-content: center; + + @include ltemd { + grid-template-columns: 1fr !important; + } } diff --git a/src-ts/lib/form/form-groups/FormGroups.tsx b/src-ts/lib/form/form-groups/FormGroups.tsx index 1a25f9a3e..868c24af3 100644 --- a/src-ts/lib/form/form-groups/FormGroups.tsx +++ b/src-ts/lib/form/form-groups/FormGroups.tsx @@ -1,5 +1,6 @@ import { ChangeEvent, FocusEvent } from 'react' +import { PageDivider } from '../../page-divider' import { FormDefinition } from '../form-definition.model' import { FormGroup } from '../form-group.model' import { FormInputModel } from '../form-input.model' @@ -33,6 +34,8 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr const tabIndex: number = getTabIndex(input, index) let inputElement: JSX.Element + + /* tslint:disable:cyclomatic-complexity */ switch (input.type) { case 'rating': @@ -90,6 +93,8 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr inputElement = ( ) break @@ -126,15 +131,21 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr group={element} renderFormInput={renderInputField} totalGroupCount={formDef.groups?.length || 0} + renderDividers={props.formDef.groupsOptions?.renderGroupDividers} /> ) }) || [] return ( -
    - {formGroups} -
    + <> +
    + {formGroups} +
    + { + props.formDef.groupsOptions?.renderGroupDividers === false && + } + ) } diff --git a/src-ts/lib/form/form-groups/form-group-item/FormGroupItem.tsx b/src-ts/lib/form/form-groups/form-group-item/FormGroupItem.tsx index 41a568ec9..3b8840156 100644 --- a/src-ts/lib/form/form-groups/form-group-item/FormGroupItem.tsx +++ b/src-ts/lib/form/form-groups/form-group-item/FormGroupItem.tsx @@ -9,6 +9,7 @@ import styles from './FormGroupItem.module.scss' interface FormGroupItemProps { group: FormGroup + renderDividers?: boolean renderFormInput: (input: FormInputModel, index: number) => JSX.Element | undefined totalGroupCount: number } @@ -19,10 +20,11 @@ interface ItemRowProps { hasMultipleGroups: boolean, instructions?: string | undefined, isMultiFieldGroup: boolean, + renderDividers?: boolean title?: string, } -const TwoColumnItem: React.FC = ({ element, formInputs, hasMultipleGroups, instructions, isMultiFieldGroup, title }: ItemRowProps) => { +const TwoColumnItem: React.FC = ({ element, formInputs, hasMultipleGroups, instructions, isMultiFieldGroup, title, renderDividers }: ItemRowProps) => { return ( <>
    @@ -41,7 +43,9 @@ const TwoColumnItem: React.FC = ({ element, formInputs, hasMultipl {formInputs}
    - + { + renderDividers !== false && + } ) } @@ -67,7 +71,7 @@ const SingleColumnItem: React.FC = ({ formInputs, hasMultipleGroup ) } -const FormGroupItem: React.FC = ({ group, renderFormInput, totalGroupCount }: FormGroupItemProps) => { +const FormGroupItem: React.FC = ({ group, renderDividers, renderFormInput, totalGroupCount }: FormGroupItemProps) => { const { instructions, title, inputs, element }: FormGroup = group const formInputs: Array = inputs?.map((field: FormInputModel, index: number) => renderFormInput(field as FormInputModel, index)) || [] @@ -77,7 +81,7 @@ const FormGroupItem: React.FC = ({ group, renderFormInput, t return isCardSet ? : - + } export default FormGroupItem diff --git a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.module.scss b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.module.scss index 0265dd561..5641206e7 100644 --- a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.module.scss +++ b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.module.scss @@ -10,6 +10,7 @@ width: 132px; height: 132px; position: relative; + margin-bottom: $space-xl; @include ltemd { width: 100%; diff --git a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx index 95987f136..a75eb442d 100644 --- a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx @@ -1,13 +1,16 @@ -import { createRef, Dispatch, FC, RefObject, SetStateAction, useEffect, useState } from 'react' +import { ChangeEvent, createRef, Dispatch, FC, RefObject, SetStateAction, useEffect, useState } from 'react' import { Button, IconOutline } from '../../../../../lib' +import { InputValue } from '../../../form-input.model' import styles from './InputImagePicker.module.scss' interface InputImagePickerProps { readonly accept?: string readonly name: string + readonly onChange: (event: ChangeEvent) => void readonly size?: number + readonly value?: InputValue } const InputImagePicker: FC = (props: InputImagePickerProps) => { @@ -48,7 +51,10 @@ const InputImagePicker: FC = (props: InputImagePickerProp accept={props.accept || '*'} className={styles.filePickerInput} ref={fileInputRef} - onChange={event => setFiles(event.target.files)} + onChange={event => { + setFiles(event.target.files) + props.onChange(event) + }} size={props.size || Infinity} /> { diff --git a/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx b/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx index ecbb51d82..a88c96fb5 100644 --- a/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx @@ -1,6 +1,7 @@ import cn from 'classnames' import { FC, FocusEvent } from 'react' +import { InputValue } from '../../../form-input.model' import { FormInputAutocompleteOption } from '../form-input-autcomplete-option.enum' import { InputWrapper } from '../input-wrapper' @@ -25,7 +26,7 @@ export interface InputTextProps { readonly spellCheck?: boolean readonly tabIndex: number readonly type: InputTextTypes - readonly value?: string | number | boolean + readonly value?: InputValue } const InputText: FC = (props: InputTextProps) => { diff --git a/src-ts/lib/form/form-input.model.ts b/src-ts/lib/form/form-input.model.ts index 991ef7b04..d42f4b784 100644 --- a/src-ts/lib/form/form-input.model.ts +++ b/src-ts/lib/form/form-input.model.ts @@ -17,6 +17,8 @@ export interface FormCard { title: string } +export type InputValue = string | boolean | FileList | undefined + export interface FormInputModel { readonly accept?: string readonly autocomplete?: FormInputAutocompleteOption @@ -44,5 +46,5 @@ export interface FormInputModel { touched?: boolean readonly type: 'card-set' | 'checkbox' | 'password' | 'radio' | 'rating' | 'text' | 'textarea' | 'image-picker' readonly validators?: ReadonlyArray - value?: string | boolean + value?: InputValue } diff --git a/src-ts/lib/form/validator-functions/validator.functions.ts b/src-ts/lib/form/validator-functions/validator.functions.ts index af1c9ad7a..9dc9663f9 100644 --- a/src-ts/lib/form/validator-functions/validator.functions.ts +++ b/src-ts/lib/form/validator-functions/validator.functions.ts @@ -1,12 +1,13 @@ import { formGetInput } from '../form-functions' +import { InputValue } from '../form-input.model' -function checkForBooleanValueAndThrowError(value: string | boolean | undefined): void { +function checkForBooleanValueAndThrowError(value: InputValue): void { if (typeof value === 'boolean') { throw new Error(`The value for the email validator cannot be a boolean`) } } -export function doesNotMatchOther(value: string | boolean | undefined, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { +export function doesNotMatchOther(value: InputValue, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { checkForBooleanValueAndThrowError(value) @@ -27,7 +28,7 @@ export function doesNotMatchOther(value: string | boolean | undefined, formEleme return `Cannot match the ${getOtherFieldLabel(otherField, otherFieldName)} value` } -export function email(value: string | boolean | undefined): string | undefined { +export function email(value: InputValue): string | undefined { checkForBooleanValueAndThrowError(value) @@ -42,10 +43,10 @@ export function email(value: string | boolean | undefined): string | undefined { const emailRegex: RegExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ - return !emailRegex.test(value) ? 'Invalid email' : undefined + return !emailRegex.test(value as string) ? 'Invalid email' : undefined } -export function password(value: string | boolean | undefined): string | undefined { +export function password(value: InputValue): string | undefined { checkForBooleanValueAndThrowError(value) @@ -64,10 +65,10 @@ export function password(value: string | boolean | undefined): string | undefine // - at least 1 symbol or number const passwordRegex: RegExp = /^(?=.*[a-zA-Z])(?=.*[#$^+=!*()@%&\d]).{8,}$/g - return !passwordRegex.test(value) ? 'Password rules: 8+ characters, 1+ letter, and 1+ number or symbol' : undefined + return !passwordRegex.test(value as string) ? 'Password rules: 8+ characters, 1+ letter, and 1+ number or symbol' : undefined } -export function matchOther(value: string | boolean | undefined, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { +export function matchOther(value: InputValue, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { checkForBooleanValueAndThrowError(value) @@ -88,11 +89,11 @@ export function matchOther(value: string | boolean | undefined, formElements?: H return `Does not match the ${getOtherFieldLabel(otherField, otherFieldName)}` } -export function required(value: string | boolean | undefined): string | undefined { +export function required(value: InputValue): string | undefined { return (value === undefined || value === '') ? 'Required' : undefined } -export function requiredIfOther(value: string | boolean | undefined, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { +export function requiredIfOther(value: InputValue, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { // if there is a value, there's no need to check the other input if (typeof value === 'string' && !!value) { @@ -129,7 +130,7 @@ export function sslUrl(value: string | undefined): string | undefined { export interface ValidatorFn { dependentField?: string, - validator: (value: string | boolean | undefined, formValues?: HTMLFormControlsCollection, otherField?: string) => string | undefined + validator: (value: InputValue, formValues?: HTMLFormControlsCollection, otherField?: string) => string | undefined } function getOtherField(formElements?: HTMLFormControlsCollection, otherFieldName?: string): HTMLInputElement { diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx index b881b8b06..5735a3748 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx @@ -1,6 +1,6 @@ -import { createRef, Dispatch, FC, RefObject, SetStateAction, useEffect, useState } from 'react' +import { FC } from 'react' -import { Button, Form, FormDefinition, formGetInputModel, FormInputModel, IconOutline } from '../../../../../lib' +import { Form, FormDefinition, formGetInputModel, FormInputModel } from '../../../../../lib' import { CreateBadgeFormField } from './create-badge-form.config' import { CreateBadgeRequest } from './create-badge-functions' @@ -17,14 +17,7 @@ const CreateBadgeForm: FC = (props: CreateBadgeFormProps) const badgeName: string = formGetInputModel(inputs, CreateBadgeFormField.badgeName).value as string const badgeDesc: string = formGetInputModel(inputs, CreateBadgeFormField.badgeDesc).value as string const badgeActive: boolean = formGetInputModel(inputs, CreateBadgeFormField.badgeActive).value as boolean - const files: FileList = formGetInputModel(inputs, CreateBadgeFormField.badgeActive).files as FileList - - console.log('generateRequest', files) - - if (!files) { - // if we don't have image file we have a problem - throw new Error(`There is no image file selected for the badge`) - } + const files: FileList = formGetInputModel(inputs, CreateBadgeFormField.file).value as FileList return { badgeActive, diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx index 3883ab4e8..5ca3e0723 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx @@ -37,18 +37,16 @@ export const createBadgeFormDef: FormDefinition = { name: CreateBadgeFormField.file, size: GamificationConfig.MAX_BADGE_IMAGE_FILE_SIZE, type: 'image-picker', - // validators: [ - // { - // validator: validatorRequired, - // }, - // ], - }, - { - checked: true, - label: 'Activate Badge', - name: CreateBadgeFormField.badgeActive, - type: 'checkbox', + validators: [ + { + validator: validatorRequired, + }, + ], }, + ], + }, + { + inputs: [ { label: 'Badge Name', name: CreateBadgeFormField.badgeName, @@ -69,7 +67,19 @@ export const createBadgeFormDef: FormDefinition = { }, ], }, + { + checked: true, + label: 'Activate Badge', + name: CreateBadgeFormField.badgeActive, + type: 'checkbox', + }, ], }, ], + groupsOptions: { + groupWrapStyles: { + gridTemplateColumns: '160px 1fr', + }, + renderGroupDividers: false, + }, } From 1a923969881489f5b4ce606fc2b4c2b06a827b75 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 23 Sep 2022 14:02:11 +0300 Subject: [PATCH 069/113] Connect create badge to API and clean up --- .../input-image-picker/InputImagePicker.tsx | 3 +- src-ts/lib/table/Table.tsx | 1 + .../BadgeCreatedModal.module.scss | 39 +++++++++++++++++++ .../badge-created-modal/BadgeCreatedModal.tsx | 29 +++++++++++++- .../gamification-admin.routes.tsx | 4 +- .../pages/create-badge/CreateBadgePage.tsx | 19 ++++++--- .../create-badge-form/CreateBadgeForm.tsx | 14 ++++--- .../create-badge-request.model.ts | 4 +- .../create-badge-store/create-badge.store.ts | 17 ++++++-- 9 files changed, 109 insertions(+), 21 deletions(-) create mode 100644 src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss diff --git a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx index a75eb442d..aa125432d 100644 --- a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx @@ -36,6 +36,7 @@ const InputImagePicker: FC = (props: InputImagePickerProp } }, [ files, + fileDataURL, ]) return ( @@ -59,7 +60,7 @@ const InputImagePicker: FC = (props: InputImagePickerProp /> { fileDataURL ? ( - + {'Badge ) : (
    UPLOAD
    IMAGE
    ) diff --git a/src-ts/lib/table/Table.tsx b/src-ts/lib/table/Table.tsx index ad3443d25..c9fd0a2f7 100644 --- a/src-ts/lib/table/Table.tsx +++ b/src-ts/lib/table/Table.tsx @@ -61,6 +61,7 @@ const Table: (props: TableProps) = columns, data, defaultSortDirectionMap, + props.onToggleSort, sort, ]) diff --git a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss new file mode 100644 index 000000000..adadea7c1 --- /dev/null +++ b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss @@ -0,0 +1,39 @@ +@import "../../../../../lib/styles/variables"; + +.wrapper { + display: flex; + flex-direction: column; + + .badge { + display: flex; + align-items: center; + margin-bottom: $space-xxl; + + .badge-image { + width: 43px; + height: 43px; + margin-right: $space-xl; + } + + .badge-image-disabled { + width: 43px; + height: 43px; + margin-right: $space-xl; + opacity: 0.5; + filter: grayscale(1); + } + + .badge-name { + font-size: 16px; + } + } + + .actions { + display: flex; + align-items: center; + + a { + margin-right: $space-md; + } + } +} \ No newline at end of file diff --git a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx index eca42d824..e6a27a8c4 100644 --- a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx +++ b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx @@ -1,8 +1,12 @@ import { FC } from 'react' -import { BaseModal } from '../../../../../lib' +import { BaseModal, Button } from '../../../../../lib' +import { badgeDetailPath } from '../../../gamification-admin.routes' +import { GameBadge } from '../../game-badge.model' +import styles from './BadgeCreatedModal.module.scss' export interface BadgeCreatedModalProps { + badge: GameBadge isOpen: boolean onClose: () => void } @@ -20,7 +24,28 @@ const BadgeCreatedModal: FC = (props: BadgeCreatedModalP size='md' title={`Badge created`} > - +
    +
    + {props.badge.badge_name} +

    {props.badge.badge_name} badge has been sucessfully created.

    +
    +
    +
    +
    ) } diff --git a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx index 108d2cf53..ee01f39d4 100644 --- a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx +++ b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx @@ -5,8 +5,8 @@ import BadgeDetailPage from './pages/badge-detail/BadgeDetailPage' import BadgeListingPage from './pages/badge-listing/BadgeListingPage' import CreateBadgePage from './pages/create-badge/CreateBadgePage' -const baseDetailPath: string = '/badge-detail' -const createBadgePath: string = '/create-badge' +export const baseDetailPath: string = '/badge-detail' +export const createBadgePath: string = '/create-badge' export const basePath: string = '/gamification-admin' diff --git a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx index 28a8e2f8b..9d34a510c 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx @@ -1,7 +1,7 @@ import { Dispatch, FC, SetStateAction, useState } from 'react' import { Breadcrumb, BreadcrumbItemModel, ContentLayout } from '../../../../lib' -import { useGamificationBreadcrumb } from '../../game-lib' +import { GameBadge, useGamificationBreadcrumb } from '../../game-lib' import { BadgeCreatedModal } from '../../game-lib/modals/badge-created-modal' import { CreateBadgeForm, createBadgeFormDef } from './create-badge-form' @@ -19,7 +19,11 @@ const CreateBadgePage: FC = () => { const [showBadgeCreatedModal, setShowBadgeCreatedModal]: [boolean, Dispatch>] = useState(false) - function onSave() { + const [createdBadge, setCreatedBadge]: [GameBadge | undefined, Dispatch>] + = useState() + + function onSave(newBadge: GameBadge): void { + setCreatedBadge(newBadge) setShowBadgeCreatedModal(true) } @@ -34,10 +38,13 @@ const CreateBadgePage: FC = () => { onSave={onSave} /> - setShowBadgeCreatedModal(false)} - /> + { + createdBadge && setShowBadgeCreatedModal(false)} + /> + } ) } diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx index 5735a3748..558132e61 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx @@ -1,6 +1,8 @@ import { FC } from 'react' import { Form, FormDefinition, formGetInputModel, FormInputModel } from '../../../../../lib' +import { GamificationConfig } from '../../../game-config' +import { GameBadge } from '../../../game-lib' import { CreateBadgeFormField } from './create-badge-form.config' import { CreateBadgeRequest } from './create-badge-functions' @@ -8,7 +10,7 @@ import { createBadgeSubmitRequestAsync } from './create-badge-functions/create-b export interface CreateBadgeFormProps { formDef: FormDefinition - onSave: () => void + onSave: (createdBadge: GameBadge) => void } const CreateBadgeForm: FC = (props: CreateBadgeFormProps) => { @@ -23,16 +25,16 @@ const CreateBadgeForm: FC = (props: CreateBadgeFormProps) badgeActive, badgeDesc, badgeName, - file: files[0], + badgeStatus: 'Active', // not used currently thus fixed field + files, + orgID: GamificationConfig.ORG_ID, } } async function saveAsync(request: CreateBadgeRequest): Promise { - console.log('saveAsync', request) - return createBadgeSubmitRequestAsync(request) - .then(() => { - props.onSave() + .then((createdBadge: GameBadge) => { + props.onSave(createdBadge) }) } diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts index 26bc449f5..a0ed3c677 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts @@ -2,5 +2,7 @@ export interface CreateBadgeRequest { badgeActive: boolean badgeDesc: string badgeName: string - file: File + badgeStatus: string + files: FileList + orgID: string } diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts index 0a83957da..9a2f83fba 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts @@ -1,10 +1,21 @@ import { EnvironmentConfig } from '../../../../../../../config' import { xhrPostAsync } from '../../../../../../../lib' +import { GameBadge } from '../../../../../game-lib' import { CreateBadgeRequest } from './create-badge-request.model' -export async function submitRequestAsync(request: CreateBadgeRequest): Promise { - console.log('submitRequestAsync', request) +export async function submitRequestAsync(request: CreateBadgeRequest): Promise { const url: string = `${EnvironmentConfig.API.V5}/gamification/badges` - await xhrPostAsync(url, request) + + const form: any = new FormData() + + // fill the form + form.append('file', request.files[0]) + form.append('organization_id', request.orgID) + form.append('badge_status', request.badgeStatus) + form.append('badge_name', request.badgeName) + form.append('badge_description', request.badgeDesc) + form.append('active', request.badgeActive ? 'true' : 'false') + + return xhrPostAsync(url, form) } From 931c3eeeaed3291e5782e011746f4a02bb24926b Mon Sep 17 00:00:00 2001 From: Brooke Date: Sat, 24 Sep 2022 09:58:47 -0700 Subject: [PATCH 070/113] TCA-322 #comment This commit updates the social share link to point to the SSR html #time 1h --- .../certificate-view/CertificateView.tsx | 18 +++++++++++++----- .../learn/learn-config/learn-config.model.ts | 1 + .../learn/learn-config/learn.default.config.ts | 1 + .../learn/learn-config/learn.prod.config.ts | 3 +++ .../user-certification-progress.store.ts | 4 ++-- src-ts/tools/learn/learn.routes.tsx | 7 ++++++- 6 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src-ts/tools/learn/course-certificate/certificate-view/CertificateView.tsx b/src-ts/tools/learn/course-certificate/certificate-view/CertificateView.tsx index ead6c3bab..7e5833a57 100644 --- a/src-ts/tools/learn/course-certificate/certificate-view/CertificateView.tsx +++ b/src-ts/tools/learn/course-certificate/certificate-view/CertificateView.tsx @@ -19,7 +19,7 @@ import { UserCompletedCertificationsProviderData, useUserCompletedCertifications, } from '../../learn-lib' -import { absoluteRootRoute, getCoursePath } from '../../learn.routes' +import { getCoursePath, getUserCertificateSsr } from '../../learn.routes' import { ActionButton } from './action-button' import { Certificate } from './certificate' @@ -35,6 +35,7 @@ interface CertificateViewProps { } const CertificateView: FC = (props: CertificateViewProps) => { + const navigate: NavigateFunction = useNavigate() const { onCertificationNotCompleted }: CertificateViewProps = props const coursePath: string = getCoursePath(props.provider, props.certification) @@ -51,6 +52,13 @@ const CertificateView: FC = (props: CertificateViewProps) ready: courseReady, }: CoursesProviderData = useCourses(props.provider, props.certification) + const certUrl: string = getUserCertificateSsr( + props.provider, + props.certification, + props.profile.handle, + `Topcoder Academy Certificate for ${course?.title} for ${props.profile.handle}` + ) + const certificationTitle: string = `${userName || props.profile.handle} - ${course?.title} Certification` const { @@ -69,7 +77,7 @@ const CertificateView: FC = (props: CertificateViewProps) }: AllCertificationsProviderData = useAllCertifications( props.provider, course?.certificationId, - {enabled: !!course?.certificationId} + { enabled: !!course?.certificationId } ) const ready: boolean = useMemo(() => ( @@ -180,15 +188,15 @@ const CertificateView: FC = (props: CertificateViewProps) /> )} diff --git a/src-ts/tools/learn/learn-config/learn-config.model.ts b/src-ts/tools/learn/learn-config/learn-config.model.ts index a4c546c7c..057d953ec 100644 --- a/src-ts/tools/learn/learn-config/learn-config.model.ts +++ b/src-ts/tools/learn/learn-config/learn-config.model.ts @@ -1,5 +1,6 @@ export interface LearnConfigModel { API: string + CERT_DOMAIN: string CERT_ELEMENT_SELECTOR: { attribute: string, value: string, diff --git a/src-ts/tools/learn/learn-config/learn.default.config.ts b/src-ts/tools/learn/learn-config/learn.default.config.ts index a29a695a1..7c6ead230 100644 --- a/src-ts/tools/learn/learn-config/learn.default.config.ts +++ b/src-ts/tools/learn/learn-config/learn.default.config.ts @@ -2,6 +2,7 @@ import { LearnConfigModel } from './learn-config.model' export const LearnConfigDefault: LearnConfigModel = { API: 'http://localhost:3001/v5/learning-paths', + CERT_DOMAIN: 'https://certificate.topcoder-dev.com', CERT_ELEMENT_SELECTOR: { attribute: 'data-id', value: 'certificate-container', diff --git a/src-ts/tools/learn/learn-config/learn.prod.config.ts b/src-ts/tools/learn/learn-config/learn.prod.config.ts index ab162a8f8..d7aeb4cba 100644 --- a/src-ts/tools/learn/learn-config/learn.prod.config.ts +++ b/src-ts/tools/learn/learn-config/learn.prod.config.ts @@ -1,6 +1,9 @@ import { LearnConfigModel } from './learn-config.model' +import { LearnConfigDefault } from './learn.default.config' export const LearnConfigProd: LearnConfigModel = { + ...LearnConfigDefault, API: 'https://api.topcoder.com/v5/learning-paths', + CERT_DOMAIN: 'https://certificate.topcoder.com', CLIENT: 'https://freecodecamp.topcoder.com', } diff --git a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/user-certification-progress.store.ts b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/user-certification-progress.store.ts index 52a78321e..5ee1a0b15 100755 --- a/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/user-certification-progress.store.ts +++ b/src-ts/tools/learn/learn-lib/user-certifications-provider/user-certifications-functions/user-certification-progress.store.ts @@ -1,5 +1,5 @@ import { LearnConfig } from '../../../learn-config' -import { getUserCertificatePath } from '../../../learn.routes' +import { getUserCertificateUrl } from '../../../learn.routes' import { learnUrlGet, learnXhrGetAsync, learnXhrPostAsync, learnXhrPutAsync } from '../../functions' import { LearnUserCertificationProgress } from './learn-user-certification-progress.model' @@ -16,7 +16,7 @@ export function completeCourse( // construct the certificate params const certificateElement: string = `[${LearnConfig.CERT_ELEMENT_SELECTOR.attribute}=${LearnConfig.CERT_ELEMENT_SELECTOR.value}]` - const certificateUrl: string = getUserCertificatePath(provider, certification, handle) + const certificateUrl: string = getUserCertificateUrl(provider, certification, handle) return updateAsync( certificationProgressId, diff --git a/src-ts/tools/learn/learn.routes.tsx b/src-ts/tools/learn/learn.routes.tsx index 3a3f8060d..f3d415044 100644 --- a/src-ts/tools/learn/learn.routes.tsx +++ b/src-ts/tools/learn/learn.routes.tsx @@ -5,6 +5,7 @@ import { CourseCompletedPage } from './course-completed' import { CourseDetailsPage } from './course-details' import { FreeCodeCamp } from './free-code-camp' import { default as Learn, toolTitle } from './Learn' +import { LearnConfig } from './learn-config' import { MyLearning } from './my-learning' import { WelcomePage } from './welcome' @@ -44,7 +45,11 @@ export function getLessonPathFromModule( return `${getCoursePath(provider, certification)}/${module}/${lesson}` } -export function getUserCertificatePath(provider: string, certification: string, handle: string): string { +export function getUserCertificateSsr(provider: string, certification: string, handle: string, title: string): string { + return `${LearnConfig.CERT_DOMAIN}/${handle}/${provider}/${certification}/${encodeURI(title)}` +} + +export function getUserCertificateUrl(provider: string, certification: string, handle: string): string { return `${window.location.origin}${getCoursePath(provider, certification)}/${handle}${LEARN_PATHS.certificate}` } From aa0b97fc2182816088cb40350ce49eda05d3d3f0 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Mon, 26 Sep 2022 18:51:24 +0300 Subject: [PATCH 071/113] better file input configs --- .../form-input/input-image-picker/InputImagePicker.tsx | 10 ++++++---- src-ts/lib/form/form-input.model.ts | 6 ++++-- .../create-badge-form/create-badge-form.config.tsx | 6 ++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx index aa125432d..43882c44c 100644 --- a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx @@ -6,10 +6,12 @@ import { InputValue } from '../../../form-input.model' import styles from './InputImagePicker.module.scss' interface InputImagePickerProps { - readonly accept?: string + readonly fileConfig?: { + readonly acceptFileType?: string + readonly maxFileSize?: number + } readonly name: string readonly onChange: (event: ChangeEvent) => void - readonly size?: number readonly value?: InputValue } @@ -49,14 +51,14 @@ const InputImagePicker: FC = (props: InputImagePickerProp { setFiles(event.target.files) props.onChange(event) }} - size={props.size || Infinity} + size={props.fileConfig?.maxFileSize || Infinity} /> { fileDataURL ? ( diff --git a/src-ts/lib/form/form-input.model.ts b/src-ts/lib/form/form-input.model.ts index d42f4b784..419d63cb7 100644 --- a/src-ts/lib/form/form-input.model.ts +++ b/src-ts/lib/form/form-input.model.ts @@ -20,7 +20,6 @@ export interface FormCard { export type InputValue = string | boolean | FileList | undefined export interface FormInputModel { - readonly accept?: string readonly autocomplete?: FormInputAutocompleteOption readonly cards?: ReadonlyArray checked?: boolean @@ -30,6 +29,10 @@ export interface FormInputModel { disabled?: boolean error?: string readonly events?: ReadonlyArray + readonly fileConfig?: { + readonly acceptFileType?: string + readonly maxFileSize?: number + } readonly files?: FileList readonly hideInlineErrors?: boolean readonly hint?: string @@ -40,7 +43,6 @@ export interface FormInputModel { readonly notTabbable?: boolean options?: ReadonlyArray readonly placeholder?: string - readonly size?: number readonly spellCheck?: boolean readonly title?: string touched?: boolean diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx index 5ca3e0723..74a0414b9 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx @@ -33,9 +33,11 @@ export const createBadgeFormDef: FormDefinition = { { inputs: [ { - accept: GamificationConfig.ACCEPTED_BADGE_MIME_TYPES, + fileConfig: { + acceptFileType: GamificationConfig.ACCEPTED_BADGE_MIME_TYPES, + maxFileSize: GamificationConfig.MAX_BADGE_IMAGE_FILE_SIZE, + }, name: CreateBadgeFormField.file, - size: GamificationConfig.MAX_BADGE_IMAGE_FILE_SIZE, type: 'image-picker', validators: [ { From eb3ab6ac7662314150a74944df2e33edbcfaa709 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Tue, 27 Sep 2022 00:48:39 +0300 Subject: [PATCH 072/113] Revert "Gamification - GAME-82" --- src-ts/lib/form/form-definition.model.ts | 3 +- .../lib/form/form-functions/form.functions.ts | 28 ++---- src-ts/lib/form/form-group.model.ts | 7 -- .../form/form-groups/FormGroups.module.scss | 6 -- src-ts/lib/form/form-groups/FormGroups.tsx | 64 +++----------- .../form-group-item/FormGroupItem.tsx | 12 +-- .../lib/form/form-groups/form-input/index.ts | 1 - .../InputImagePicker.module.scss | 40 --------- .../input-image-picker/InputImagePicker.tsx | 74 ---------------- .../form-input/input-image-picker/index.ts | 1 - .../input-text/InputText.module.scss | 29 +------ .../form-input/input-text/InputText.tsx | 13 +-- src-ts/lib/form/form-input.model.ts | 12 +-- .../validator.functions.ts | 21 +++-- src-ts/lib/table/Table.tsx | 1 - .../game-config/gamification-config.model.ts | 2 - .../gamification.default.config.ts | 2 - .../BadgeCreatedModal.module.scss | 39 --------- .../badge-created-modal/BadgeCreatedModal.tsx | 53 ----------- .../modals/badge-created-modal/index.ts | 1 - .../gamification-admin.routes.tsx | 4 +- .../create-badge/CreateBadgePage.module.scss | 15 +++- .../pages/create-badge/CreateBadgePage.tsx | 32 ++----- .../create-badge-form/CreateBadgeForm.tsx | 50 ----------- .../create-badge-form.config.tsx | 87 ------------------- .../create-badge-request.model.ts | 8 -- .../create-badge-store/create-badge.store.ts | 21 ----- .../create-badge-store/index.ts | 2 - .../create-badge.functions.ts | 5 -- .../create-badge-functions/index.ts | 2 - .../create-badge/create-badge-form/index.ts | 2 - 31 files changed, 63 insertions(+), 574 deletions(-) delete mode 100644 src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.module.scss delete mode 100644 src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx delete mode 100644 src-ts/lib/form/form-groups/form-input/input-image-picker/index.ts delete mode 100644 src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss delete mode 100644 src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx delete mode 100644 src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/index.ts delete mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx delete mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx delete mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts delete mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts delete mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/index.ts delete mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge.functions.ts delete mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/index.ts delete mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/index.ts diff --git a/src-ts/lib/form/form-definition.model.ts b/src-ts/lib/form/form-definition.model.ts index efd292f8f..fef7d8c58 100644 --- a/src-ts/lib/form/form-definition.model.ts +++ b/src-ts/lib/form/form-definition.model.ts @@ -1,4 +1,4 @@ -import { FormButton, FormGroup, FormGroupOptions } from '.' +import { FormButton, FormGroup } from '.' export type FormAction = 'save' | 'submit' | undefined @@ -10,7 +10,6 @@ export interface FormButtons { export interface FormDefinition { readonly buttons: FormButtons readonly groups?: Array - readonly groupsOptions?: FormGroupOptions readonly shortName?: string readonly subtitle?: string readonly successMessage?: string diff --git a/src-ts/lib/form/form-functions/form.functions.ts b/src-ts/lib/form/form-functions/form.functions.ts index 1c4eaf1a3..5f5e72246 100644 --- a/src-ts/lib/form/form-functions/form.functions.ts +++ b/src-ts/lib/form/form-functions/form.functions.ts @@ -33,13 +33,9 @@ export function initializeValues(inputs: Array, formValues?: inputs .filter(input => !input.dirty && !input.touched) .forEach(input => { - if (input.type === 'checkbox') { - input.value = input.checked || false - } else { - input.value = !!(formValues as any)?.hasOwnProperty(input.name) - ? (formValues as any)[input.name] - : undefined - } + input.value = !!(formValues as any)?.hasOwnProperty(input.name) + ? (formValues as any)[input.name] + : undefined }) } @@ -68,7 +64,6 @@ export async function onSubmitAsync( formValue: T, save: (value: T) => Promise, onSuccess?: () => void, - customValidateForm?: (formElements: HTMLFormControlsCollection, event: ValidationEvent, inputs: ReadonlyArray) => boolean ): Promise { event.preventDefault() @@ -85,7 +80,7 @@ export async function onSubmitAsync( // want to have it look like the submit succeeded const formValues: HTMLFormControlsCollection = (event.target as HTMLFormElement).elements if (action === 'submit') { - const isValid: boolean = (customValidateForm || validateForm)(formValues, action, inputs) + const isValid: boolean = validateForm(formValues, action, inputs) if (!isValid) { return Promise.reject() } @@ -120,22 +115,13 @@ function handleFieldEvent(input: HTMLInputElement | HTMLTextAreaElement, inpu const inputDef: FormInputModel = getInputModel(inputs, input.name) - const inputEl: HTMLInputElement = input as HTMLInputElement - if (event === 'change') { inputDef.dirty = input.value !== originalValue } inputDef.touched = true // set the def value - if (input.type === 'checkbox') { - inputDef.value = inputEl.checked - inputDef.checked = inputEl.checked - } else if (input.type === 'file') { - inputDef.value = inputEl.files || undefined - } else { - inputDef.value = input.value - } + inputDef.value = input.value // now let's validate the field const formElements: HTMLFormControlsCollection = (input.form as HTMLFormElement).elements @@ -185,9 +171,7 @@ function validateField(formInputDef: FormInputModel, formElements: HTMLFormContr }) } -export type ValidationEvent = 'blur' | 'change' | 'submit' | 'initial' - -export function validateForm(formElements: HTMLFormControlsCollection, event: ValidationEvent, inputs: ReadonlyArray): boolean { +export function validateForm(formElements: HTMLFormControlsCollection, event: 'blur' | 'change' | 'submit' | 'initial', inputs: ReadonlyArray): boolean { const errors: ReadonlyArray = inputs?.filter(formInputDef => { formInputDef.dirty = formInputDef.dirty || event === 'submit' validateField(formInputDef, formElements, event) diff --git a/src-ts/lib/form/form-group.model.ts b/src-ts/lib/form/form-group.model.ts index 7f90a5418..7fe97df9f 100644 --- a/src-ts/lib/form/form-group.model.ts +++ b/src-ts/lib/form/form-group.model.ts @@ -1,5 +1,3 @@ -import { CSSProperties } from 'react' - import { FormInputModel } from './form-input.model' export interface FormGroup { @@ -8,8 +6,3 @@ export interface FormGroup { readonly instructions?: string readonly title?: string } - -export interface FormGroupOptions { - groupWrapStyles?: CSSProperties - renderGroupDividers?: boolean -} diff --git a/src-ts/lib/form/form-groups/FormGroups.module.scss b/src-ts/lib/form/form-groups/FormGroups.module.scss index 3bf262416..6c3e7a4b0 100644 --- a/src-ts/lib/form/form-groups/FormGroups.module.scss +++ b/src-ts/lib/form/form-groups/FormGroups.module.scss @@ -1,11 +1,5 @@ -@import "../../styles/includes"; - .form-groups { display: grid; grid-template-columns: 1fr; justify-content: center; - - @include ltemd { - grid-template-columns: 1fr !important; - } } diff --git a/src-ts/lib/form/form-groups/FormGroups.tsx b/src-ts/lib/form/form-groups/FormGroups.tsx index 868c24af3..2562d0146 100644 --- a/src-ts/lib/form/form-groups/FormGroups.tsx +++ b/src-ts/lib/form/form-groups/FormGroups.tsx @@ -1,13 +1,12 @@ import { ChangeEvent, FocusEvent } from 'react' -import { PageDivider } from '../../page-divider' import { FormDefinition } from '../form-definition.model' import { FormGroup } from '../form-group.model' import { FormInputModel } from '../form-input.model' import { FormCardSet } from './form-card-set' import FormGroupItem from './form-group-item/FormGroupItem' -import { InputImagePicker, InputRating, InputText, InputTextarea } from './form-input' +import { InputRating, InputText, InputTextarea } from './form-input' import { FormInputRow } from './form-input-row' import { InputTextTypes } from './form-input/input-text/InputText' import FormRadio from './form-radio' @@ -24,18 +23,15 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr const { formDef, onBlur, onChange }: FormGroupsProps = props - function getTabIndex(input: FormInputModel, index: number): number { + const getTabIndex: (input: FormInputModel, index: number) => number = (input, index) => { const tabIndex: number = input.notTabbable ? -1 : index + 1 + (formDef.tabIndexStart || 0) return tabIndex } - function renderInputField(input: FormInputModel, index: number): JSX.Element | undefined { - + const renderInputField: (input: FormInputModel, index: number) => JSX.Element | undefined = (input, index) => { const tabIndex: number = getTabIndex(input, index) let inputElement: JSX.Element - - /* tslint:disable:cyclomatic-complexity */ switch (input.type) { case 'rating': @@ -44,10 +40,11 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr {...input} onChange={onChange} tabIndex={tabIndex} - value={input.value as number | undefined} + value={input.value} /> ) break + case 'textarea': inputElement = ( JSX.Element = (props: FormGroupsPr onBlur={onBlur} onChange={onChange} tabIndex={tabIndex} - value={input.value as string | undefined} + value={input.value} /> ) break case 'checkbox': - inputElement = ( - - ) - break case 'radio': inputElement = ( JSX.Element = (props: FormGroupsPr /> ) break - case 'image-picker': - inputElement = ( - - ) - break default: inputElement = ( JSX.Element = (props: FormGroupsPr onChange={onChange} tabIndex={tabIndex} type={input.type as InputTextTypes || 'text'} - value={input.value as string | undefined} + value={input.value} /> ) break @@ -123,29 +100,14 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr ) } - const formGroups: Array = formDef?.groups - ?.map((element: FormGroup, index: number) => { - return ( - - ) - }) - || [] + const formGroups: Array = formDef?.groups?.map((element: FormGroup, index: number) => { + return + }) || [] return ( - <> -
    - {formGroups} -
    - { - props.formDef.groupsOptions?.renderGroupDividers === false && - } - +
    + {formGroups} +
    ) } diff --git a/src-ts/lib/form/form-groups/form-group-item/FormGroupItem.tsx b/src-ts/lib/form/form-groups/form-group-item/FormGroupItem.tsx index 3b8840156..41a568ec9 100644 --- a/src-ts/lib/form/form-groups/form-group-item/FormGroupItem.tsx +++ b/src-ts/lib/form/form-groups/form-group-item/FormGroupItem.tsx @@ -9,7 +9,6 @@ import styles from './FormGroupItem.module.scss' interface FormGroupItemProps { group: FormGroup - renderDividers?: boolean renderFormInput: (input: FormInputModel, index: number) => JSX.Element | undefined totalGroupCount: number } @@ -20,11 +19,10 @@ interface ItemRowProps { hasMultipleGroups: boolean, instructions?: string | undefined, isMultiFieldGroup: boolean, - renderDividers?: boolean title?: string, } -const TwoColumnItem: React.FC = ({ element, formInputs, hasMultipleGroups, instructions, isMultiFieldGroup, title, renderDividers }: ItemRowProps) => { +const TwoColumnItem: React.FC = ({ element, formInputs, hasMultipleGroups, instructions, isMultiFieldGroup, title }: ItemRowProps) => { return ( <>
    @@ -43,9 +41,7 @@ const TwoColumnItem: React.FC = ({ element, formInputs, hasMultipl {formInputs}
    - { - renderDividers !== false && - } + ) } @@ -71,7 +67,7 @@ const SingleColumnItem: React.FC = ({ formInputs, hasMultipleGroup ) } -const FormGroupItem: React.FC = ({ group, renderDividers, renderFormInput, totalGroupCount }: FormGroupItemProps) => { +const FormGroupItem: React.FC = ({ group, renderFormInput, totalGroupCount }: FormGroupItemProps) => { const { instructions, title, inputs, element }: FormGroup = group const formInputs: Array = inputs?.map((field: FormInputModel, index: number) => renderFormInput(field as FormInputModel, index)) || [] @@ -81,7 +77,7 @@ const FormGroupItem: React.FC = ({ group, renderDividers, re return isCardSet ? : - + } export default FormGroupItem diff --git a/src-ts/lib/form/form-groups/form-input/index.ts b/src-ts/lib/form/form-groups/form-input/index.ts index 4e43914cb..46f16f8c4 100644 --- a/src-ts/lib/form/form-groups/form-input/index.ts +++ b/src-ts/lib/form/form-groups/form-input/index.ts @@ -1,4 +1,3 @@ -export * from './input-image-picker' export * from './form-input-autcomplete-option.enum' export * from './input-rating' export * from './input-select' diff --git a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.module.scss b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.module.scss deleted file mode 100644 index 5641206e7..000000000 --- a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.module.scss +++ /dev/null @@ -1,40 +0,0 @@ -@import "../../../../styles/includes"; -@import "../../../../styles/variables"; - -.filePicker { - display: flex; - align-items: center; - justify-content: center; - background-color: $black-5; - border-radius: 8px; - width: 132px; - height: 132px; - position: relative; - margin-bottom: $space-xl; - - @include ltemd { - width: 100%; - } - - .filePickerPlaceholder { - color: $turq-160; - text-align: center; - font-weight: $font-weight-bold; - } - - .filePickerPencil { - position: absolute; - top: 0; - right: 0; - color: $turq-160; - } - - .filePickerInput { - display: none; - } - - .badgeImage { - width: 72px; - height: 72px; - } -} \ No newline at end of file diff --git a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx deleted file mode 100644 index 43882c44c..000000000 --- a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { ChangeEvent, createRef, Dispatch, FC, RefObject, SetStateAction, useEffect, useState } from 'react' - -import { Button, IconOutline } from '../../../../../lib' -import { InputValue } from '../../../form-input.model' - -import styles from './InputImagePicker.module.scss' - -interface InputImagePickerProps { - readonly fileConfig?: { - readonly acceptFileType?: string - readonly maxFileSize?: number - } - readonly name: string - readonly onChange: (event: ChangeEvent) => void - readonly value?: InputValue -} - -const InputImagePicker: FC = (props: InputImagePickerProps) => { - - const fileInputRef: RefObject = createRef() - - // tslint:disable-next-line:no-null-keyword - const [files, setFiles]: [FileList | null, Dispatch>] = useState(null) - const [fileDataURL, setFileDataURL]: [string | undefined, Dispatch>] = useState() - - useEffect(() => { - if (files && files.length) { - const fileReader: FileReader = new FileReader() - fileReader.onload = e => { - const { result }: any = e.target - if (result) { - setFileDataURL(result) - } - } - fileReader.readAsDataURL(files[0]) - } else if (fileDataURL) { - setFileDataURL(undefined) - } - }, [ - files, - fileDataURL, - ]) - - return ( -
    -
    - ) -} - -export default InputImagePicker diff --git a/src-ts/lib/form/form-groups/form-input/input-image-picker/index.ts b/src-ts/lib/form/form-groups/form-input/input-image-picker/index.ts deleted file mode 100644 index ceeb9aa9d..000000000 --- a/src-ts/lib/form/form-groups/form-input/input-image-picker/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as InputImagePicker } from './InputImagePicker' diff --git a/src-ts/lib/form/form-groups/form-input/input-text/InputText.module.scss b/src-ts/lib/form/form-groups/form-input/input-text/InputText.module.scss index ac7f33476..7b47d447d 100644 --- a/src-ts/lib/form/form-groups/form-input/input-text/InputText.module.scss +++ b/src-ts/lib/form/form-groups/form-input/input-text/InputText.module.scss @@ -30,32 +30,9 @@ } &.checkbox { - @extend .body-small; - color: $black-60; - box-sizing: border-box; - border: 0; - width: 100%; - padding: 0; - margin: 0; - height: auto; - border-radius: 0; - - &:focus { - box-shadow: none; - border: none; - outline: none; - color: $black-100; - } - - &:disabled { - background-color: $black-10; - } - - &.checkbox { - & { - width: 20px; - height: 20px; - } + & { + width: 20px; + height: 20px; } } } diff --git a/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx b/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx index a88c96fb5..21e94e307 100644 --- a/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx @@ -1,7 +1,6 @@ import cn from 'classnames' import { FC, FocusEvent } from 'react' -import { InputValue } from '../../../form-input.model' import { FormInputAutocompleteOption } from '../form-input-autcomplete-option.enum' import { InputWrapper } from '../input-wrapper' @@ -9,9 +8,8 @@ import styles from './InputText.module.scss' export type InputTextTypes = 'checkbox' | 'password' | 'text' -export interface InputTextProps { +interface InputTextProps { readonly autocomplete?: FormInputAutocompleteOption - readonly checked?: boolean readonly className?: string readonly dirty?: boolean readonly disabled?: boolean @@ -26,15 +24,11 @@ export interface InputTextProps { readonly spellCheck?: boolean readonly tabIndex: number readonly type: InputTextTypes - readonly value?: InputValue + readonly value?: string | number } const InputText: FC = (props: InputTextProps) => { - const defaultValue: string | number | undefined = props.type === 'checkbox' && !!props.checked - ? 'on' - : props.value as string | number | undefined - return ( = (props: InputTextProps) => { > - checked?: boolean readonly className?: string readonly dependentFields?: Array dirty?: boolean disabled?: boolean error?: string readonly events?: ReadonlyArray - readonly fileConfig?: { - readonly acceptFileType?: string - readonly maxFileSize?: number - } - readonly files?: FileList readonly hideInlineErrors?: boolean readonly hint?: string readonly id?: string @@ -46,7 +38,7 @@ export interface FormInputModel { readonly spellCheck?: boolean readonly title?: string touched?: boolean - readonly type: 'card-set' | 'checkbox' | 'password' | 'radio' | 'rating' | 'text' | 'textarea' | 'image-picker' + readonly type: 'card-set' | 'checkbox' | 'password' | 'radio' | 'rating' | 'text' | 'textarea' readonly validators?: ReadonlyArray - value?: InputValue + value?: string } diff --git a/src-ts/lib/form/validator-functions/validator.functions.ts b/src-ts/lib/form/validator-functions/validator.functions.ts index 9dc9663f9..af1c9ad7a 100644 --- a/src-ts/lib/form/validator-functions/validator.functions.ts +++ b/src-ts/lib/form/validator-functions/validator.functions.ts @@ -1,13 +1,12 @@ import { formGetInput } from '../form-functions' -import { InputValue } from '../form-input.model' -function checkForBooleanValueAndThrowError(value: InputValue): void { +function checkForBooleanValueAndThrowError(value: string | boolean | undefined): void { if (typeof value === 'boolean') { throw new Error(`The value for the email validator cannot be a boolean`) } } -export function doesNotMatchOther(value: InputValue, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { +export function doesNotMatchOther(value: string | boolean | undefined, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { checkForBooleanValueAndThrowError(value) @@ -28,7 +27,7 @@ export function doesNotMatchOther(value: InputValue, formElements?: HTMLFormCont return `Cannot match the ${getOtherFieldLabel(otherField, otherFieldName)} value` } -export function email(value: InputValue): string | undefined { +export function email(value: string | boolean | undefined): string | undefined { checkForBooleanValueAndThrowError(value) @@ -43,10 +42,10 @@ export function email(value: InputValue): string | undefined { const emailRegex: RegExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ - return !emailRegex.test(value as string) ? 'Invalid email' : undefined + return !emailRegex.test(value) ? 'Invalid email' : undefined } -export function password(value: InputValue): string | undefined { +export function password(value: string | boolean | undefined): string | undefined { checkForBooleanValueAndThrowError(value) @@ -65,10 +64,10 @@ export function password(value: InputValue): string | undefined { // - at least 1 symbol or number const passwordRegex: RegExp = /^(?=.*[a-zA-Z])(?=.*[#$^+=!*()@%&\d]).{8,}$/g - return !passwordRegex.test(value as string) ? 'Password rules: 8+ characters, 1+ letter, and 1+ number or symbol' : undefined + return !passwordRegex.test(value) ? 'Password rules: 8+ characters, 1+ letter, and 1+ number or symbol' : undefined } -export function matchOther(value: InputValue, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { +export function matchOther(value: string | boolean | undefined, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { checkForBooleanValueAndThrowError(value) @@ -89,11 +88,11 @@ export function matchOther(value: InputValue, formElements?: HTMLFormControlsCol return `Does not match the ${getOtherFieldLabel(otherField, otherFieldName)}` } -export function required(value: InputValue): string | undefined { +export function required(value: string | boolean | undefined): string | undefined { return (value === undefined || value === '') ? 'Required' : undefined } -export function requiredIfOther(value: InputValue, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { +export function requiredIfOther(value: string | boolean | undefined, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { // if there is a value, there's no need to check the other input if (typeof value === 'string' && !!value) { @@ -130,7 +129,7 @@ export function sslUrl(value: string | undefined): string | undefined { export interface ValidatorFn { dependentField?: string, - validator: (value: InputValue, formValues?: HTMLFormControlsCollection, otherField?: string) => string | undefined + validator: (value: string | boolean | undefined, formValues?: HTMLFormControlsCollection, otherField?: string) => string | undefined } function getOtherField(formElements?: HTMLFormControlsCollection, otherFieldName?: string): HTMLInputElement { diff --git a/src-ts/lib/table/Table.tsx b/src-ts/lib/table/Table.tsx index c9fd0a2f7..ad3443d25 100644 --- a/src-ts/lib/table/Table.tsx +++ b/src-ts/lib/table/Table.tsx @@ -61,7 +61,6 @@ const Table: (props: TableProps) = columns, data, defaultSortDirectionMap, - props.onToggleSort, sort, ]) diff --git a/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts b/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts index c397769c1..c1b0124fe 100644 --- a/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts +++ b/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts @@ -1,6 +1,4 @@ export interface GamificationConfigModel { - ACCEPTED_BADGE_MIME_TYPES: string - MAX_BADGE_IMAGE_FILE_SIZE: number ORG_ID: string PAGE_SIZE: number } diff --git a/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts b/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts index b35ada502..b2c40e4d8 100644 --- a/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts +++ b/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts @@ -1,8 +1,6 @@ import { GamificationConfigModel } from './gamification-config.model' export const GamificationConfigDefault: GamificationConfigModel = { - ACCEPTED_BADGE_MIME_TYPES: 'image/svg+xml,image/svg', - MAX_BADGE_IMAGE_FILE_SIZE: 5000000, // 5mb in bytes ORG_ID: '6052dd9b-ea80-494b-b258-edd1331e27a3', PAGE_SIZE: 12, } diff --git a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss deleted file mode 100644 index adadea7c1..000000000 --- a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss +++ /dev/null @@ -1,39 +0,0 @@ -@import "../../../../../lib/styles/variables"; - -.wrapper { - display: flex; - flex-direction: column; - - .badge { - display: flex; - align-items: center; - margin-bottom: $space-xxl; - - .badge-image { - width: 43px; - height: 43px; - margin-right: $space-xl; - } - - .badge-image-disabled { - width: 43px; - height: 43px; - margin-right: $space-xl; - opacity: 0.5; - filter: grayscale(1); - } - - .badge-name { - font-size: 16px; - } - } - - .actions { - display: flex; - align-items: center; - - a { - margin-right: $space-md; - } - } -} \ No newline at end of file diff --git a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx deleted file mode 100644 index e6a27a8c4..000000000 --- a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { FC } from 'react' - -import { BaseModal, Button } from '../../../../../lib' -import { badgeDetailPath } from '../../../gamification-admin.routes' -import { GameBadge } from '../../game-badge.model' - -import styles from './BadgeCreatedModal.module.scss' -export interface BadgeCreatedModalProps { - badge: GameBadge - isOpen: boolean - onClose: () => void -} - -const BadgeCreatedModal: FC = (props: BadgeCreatedModalProps) => { - - function onClose(): void { - props.onClose() - } - - return ( - -
    -
    - {props.badge.badge_name} -

    {props.badge.badge_name} badge has been sucessfully created.

    -
    -
    -
    -
    -
    - ) -} - -export default BadgeCreatedModal diff --git a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/index.ts b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/index.ts deleted file mode 100644 index 2e8ce2cda..000000000 --- a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as BadgeCreatedModal } from './BadgeCreatedModal' diff --git a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx index ee01f39d4..108d2cf53 100644 --- a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx +++ b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx @@ -5,8 +5,8 @@ import BadgeDetailPage from './pages/badge-detail/BadgeDetailPage' import BadgeListingPage from './pages/badge-listing/BadgeListingPage' import CreateBadgePage from './pages/create-badge/CreateBadgePage' -export const baseDetailPath: string = '/badge-detail' -export const createBadgePath: string = '/create-badge' +const baseDetailPath: string = '/badge-detail' +const createBadgePath: string = '/create-badge' export const basePath: string = '/gamification-admin' diff --git a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss index fac1cbcbc..7da13b030 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss +++ b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss @@ -1,6 +1,17 @@ -@import "../../../../lib/styles/variables"; +.contentLayout { + width: 100%; + padding-bottom: 0; + + .contentLayout-outer { + width: 100%; + + .contentLayout-inner { + width: 100%; + overflow: visible; + } + } +} .container { display: flex; - padding-top: $space-xxxxl; } diff --git a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx index 9d34a510c..22d7e59d6 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx @@ -1,10 +1,8 @@ -import { Dispatch, FC, SetStateAction, useState } from 'react' +import { FC } from 'react' import { Breadcrumb, BreadcrumbItemModel, ContentLayout } from '../../../../lib' -import { GameBadge, useGamificationBreadcrumb } from '../../game-lib' -import { BadgeCreatedModal } from '../../game-lib/modals/badge-created-modal' +import { useGamificationBreadcrumb } from '../../game-lib' -import { CreateBadgeForm, createBadgeFormDef } from './create-badge-form' import styles from './CreateBadgePage.module.scss' const CreateBadgePage: FC = () => { @@ -16,35 +14,17 @@ const CreateBadgePage: FC = () => { }, ]) - const [showBadgeCreatedModal, setShowBadgeCreatedModal]: [boolean, Dispatch>] - = useState(false) - - const [createdBadge, setCreatedBadge]: [GameBadge | undefined, Dispatch>] - = useState() - - function onSave(newBadge: GameBadge): void { - setCreatedBadge(newBadge) - setShowBadgeCreatedModal(true) - } - return (
    - +
    - { - createdBadge && setShowBadgeCreatedModal(false)} - /> - }
    ) } diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx deleted file mode 100644 index 558132e61..000000000 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import { FC } from 'react' - -import { Form, FormDefinition, formGetInputModel, FormInputModel } from '../../../../../lib' -import { GamificationConfig } from '../../../game-config' -import { GameBadge } from '../../../game-lib' - -import { CreateBadgeFormField } from './create-badge-form.config' -import { CreateBadgeRequest } from './create-badge-functions' -import { createBadgeSubmitRequestAsync } from './create-badge-functions/create-badge-store' - -export interface CreateBadgeFormProps { - formDef: FormDefinition - onSave: (createdBadge: GameBadge) => void -} - -const CreateBadgeForm: FC = (props: CreateBadgeFormProps) => { - - function generateRequest(inputs: ReadonlyArray): CreateBadgeRequest { - const badgeName: string = formGetInputModel(inputs, CreateBadgeFormField.badgeName).value as string - const badgeDesc: string = formGetInputModel(inputs, CreateBadgeFormField.badgeDesc).value as string - const badgeActive: boolean = formGetInputModel(inputs, CreateBadgeFormField.badgeActive).value as boolean - const files: FileList = formGetInputModel(inputs, CreateBadgeFormField.file).value as FileList - - return { - badgeActive, - badgeDesc, - badgeName, - badgeStatus: 'Active', // not used currently thus fixed field - files, - orgID: GamificationConfig.ORG_ID, - } - } - - async function saveAsync(request: CreateBadgeRequest): Promise { - return createBadgeSubmitRequestAsync(request) - .then((createdBadge: GameBadge) => { - props.onSave(createdBadge) - }) - } - - return ( - - ) -} - -export default CreateBadgeForm diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx deleted file mode 100644 index 74a0414b9..000000000 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx +++ /dev/null @@ -1,87 +0,0 @@ -import { FormDefinition, IconOutline, validatorRequired } from '../../../../../lib' -import { GamificationConfig } from '../../../game-config' - -export enum CreateBadgeFormField { - badgeActive = 'badgeActive', - badgeName = 'badgeName', - badgeDesc = 'badgeDesc', - file = 'file', -} - -export const createBadgeFormDef: FormDefinition = { - buttons: { - primaryGroup: [ - { - buttonStyle: 'primary', - isSubmit: true, - label: 'Save Badge', - onClick: (e) => { }, - size: 'lg', - type: 'submit', - }, - ], - secondaryGroup: [ - { - buttonStyle: 'icon-bordered', - icon: IconOutline.ChevronLeftIcon, - route: '/gamification-admin', - size: 'lg', - }, - ], - }, - groups: [ - { - inputs: [ - { - fileConfig: { - acceptFileType: GamificationConfig.ACCEPTED_BADGE_MIME_TYPES, - maxFileSize: GamificationConfig.MAX_BADGE_IMAGE_FILE_SIZE, - }, - name: CreateBadgeFormField.file, - type: 'image-picker', - validators: [ - { - validator: validatorRequired, - }, - ], - }, - ], - }, - { - inputs: [ - { - label: 'Badge Name', - name: CreateBadgeFormField.badgeName, - type: 'text', - validators: [ - { - validator: validatorRequired, - }, - ], - }, - { - label: 'Badge Description', - name: CreateBadgeFormField.badgeDesc, - type: 'textarea', - validators: [ - { - validator: validatorRequired, - }, - ], - }, - { - checked: true, - label: 'Activate Badge', - name: CreateBadgeFormField.badgeActive, - type: 'checkbox', - }, - ], - }, - ], - groupsOptions: { - groupWrapStyles: { - gridTemplateColumns: '160px 1fr', - }, - renderGroupDividers: false, - }, -} diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts deleted file mode 100644 index a0ed3c677..000000000 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface CreateBadgeRequest { - badgeActive: boolean - badgeDesc: string - badgeName: string - badgeStatus: string - files: FileList - orgID: string -} diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts deleted file mode 100644 index 9a2f83fba..000000000 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { EnvironmentConfig } from '../../../../../../../config' -import { xhrPostAsync } from '../../../../../../../lib' -import { GameBadge } from '../../../../../game-lib' - -import { CreateBadgeRequest } from './create-badge-request.model' - -export async function submitRequestAsync(request: CreateBadgeRequest): Promise { - const url: string = `${EnvironmentConfig.API.V5}/gamification/badges` - - const form: any = new FormData() - - // fill the form - form.append('file', request.files[0]) - form.append('organization_id', request.orgID) - form.append('badge_status', request.badgeStatus) - form.append('badge_name', request.badgeName) - form.append('badge_description', request.badgeDesc) - form.append('active', request.badgeActive ? 'true' : 'false') - - return xhrPostAsync(url, form) -} diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/index.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/index.ts deleted file mode 100644 index 5f0511634..000000000 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './create-badge-request.model' -export { submitRequestAsync as createBadgeSubmitRequestAsync } from './create-badge.store' diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge.functions.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge.functions.ts deleted file mode 100644 index ddce9a486..000000000 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge.functions.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { CreateBadgeRequest, createBadgeSubmitRequestAsync } from './create-badge-store' - -export async function submitRequestAsync(request: CreateBadgeRequest): Promise { - return createBadgeSubmitRequestAsync(request) -} diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/index.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/index.ts deleted file mode 100644 index 82368d47a..000000000 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { type CreateBadgeRequest } from './create-badge-store' -export { submitRequestAsync as contactSupportSubmitRequestAsync } from './create-badge.functions' diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/index.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/index.ts deleted file mode 100644 index a323b7903..000000000 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { default as CreateBadgeForm } from './CreateBadgeForm' -export { createBadgeFormDef } from './create-badge-form.config' From d05063d63bd82c04d0b42b500e616b25cc141db1 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Tue, 27 Sep 2022 12:38:43 +0300 Subject: [PATCH 073/113] GAME-121 & GAME-122 --- .../form-input/input-image-picker/InputImagePicker.tsx | 2 +- .../modals/badge-created-modal/BadgeCreatedModal.tsx | 5 +++-- .../pages/create-badge/CreateBadgePage.tsx | 5 ++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx index 43882c44c..d724b8ade 100644 --- a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx @@ -62,7 +62,7 @@ const InputImagePicker: FC = (props: InputImagePickerProp /> { fileDataURL ? ( - {'Badge + {'Badge ) : (
    UPLOAD
    IMAGE
    ) diff --git a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx index e6a27a8c4..375c47192 100644 --- a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx +++ b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx @@ -23,6 +23,7 @@ const BadgeCreatedModal: FC = (props: BadgeCreatedModalP open={props.isOpen} size='md' title={`Badge created`} + closeOnOverlayClick={false} >
    @@ -40,9 +41,9 @@ const BadgeCreatedModal: FC = (props: BadgeCreatedModalP route={badgeDetailPath(props.badge.id)} />
    diff --git a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx index 9d34a510c..e535b48e9 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx @@ -42,7 +42,10 @@ const CreateBadgePage: FC = () => { createdBadge && setShowBadgeCreatedModal(false)} + onClose={() => { + setCreatedBadge(undefined) + setShowBadgeCreatedModal(false) + }} /> } From 385c559de9f07c1dd8a985c953fbc1a92ce9dc16 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Tue, 27 Sep 2022 12:48:56 +0300 Subject: [PATCH 074/113] Revert "Revert "Gamification - GAME-82"" --- src-ts/lib/form/form-definition.model.ts | 3 +- .../lib/form/form-functions/form.functions.ts | 28 ++++-- src-ts/lib/form/form-group.model.ts | 7 ++ .../form/form-groups/FormGroups.module.scss | 6 ++ src-ts/lib/form/form-groups/FormGroups.tsx | 64 +++++++++++--- .../form-group-item/FormGroupItem.tsx | 12 ++- .../lib/form/form-groups/form-input/index.ts | 1 + .../InputImagePicker.module.scss | 40 +++++++++ .../input-image-picker/InputImagePicker.tsx | 74 ++++++++++++++++ .../form-input/input-image-picker/index.ts | 1 + .../input-text/InputText.module.scss | 29 ++++++- .../form-input/input-text/InputText.tsx | 13 ++- src-ts/lib/form/form-input.model.ts | 12 ++- .../validator.functions.ts | 21 ++--- src-ts/lib/table/Table.tsx | 1 + .../game-config/gamification-config.model.ts | 2 + .../gamification.default.config.ts | 2 + .../BadgeCreatedModal.module.scss | 39 +++++++++ .../badge-created-modal/BadgeCreatedModal.tsx | 53 +++++++++++ .../modals/badge-created-modal/index.ts | 1 + .../gamification-admin.routes.tsx | 4 +- .../create-badge/CreateBadgePage.module.scss | 15 +--- .../pages/create-badge/CreateBadgePage.tsx | 32 +++++-- .../create-badge-form/CreateBadgeForm.tsx | 50 +++++++++++ .../create-badge-form.config.tsx | 87 +++++++++++++++++++ .../create-badge-request.model.ts | 8 ++ .../create-badge-store/create-badge.store.ts | 21 +++++ .../create-badge-store/index.ts | 2 + .../create-badge.functions.ts | 5 ++ .../create-badge-functions/index.ts | 2 + .../create-badge/create-badge-form/index.ts | 2 + 31 files changed, 574 insertions(+), 63 deletions(-) create mode 100644 src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.module.scss create mode 100644 src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx create mode 100644 src-ts/lib/form/form-groups/form-input/input-image-picker/index.ts create mode 100644 src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss create mode 100644 src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx create mode 100644 src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/index.ts create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/index.ts create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge.functions.ts create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/index.ts create mode 100644 src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/index.ts diff --git a/src-ts/lib/form/form-definition.model.ts b/src-ts/lib/form/form-definition.model.ts index fef7d8c58..efd292f8f 100644 --- a/src-ts/lib/form/form-definition.model.ts +++ b/src-ts/lib/form/form-definition.model.ts @@ -1,4 +1,4 @@ -import { FormButton, FormGroup } from '.' +import { FormButton, FormGroup, FormGroupOptions } from '.' export type FormAction = 'save' | 'submit' | undefined @@ -10,6 +10,7 @@ export interface FormButtons { export interface FormDefinition { readonly buttons: FormButtons readonly groups?: Array + readonly groupsOptions?: FormGroupOptions readonly shortName?: string readonly subtitle?: string readonly successMessage?: string diff --git a/src-ts/lib/form/form-functions/form.functions.ts b/src-ts/lib/form/form-functions/form.functions.ts index 5f5e72246..1c4eaf1a3 100644 --- a/src-ts/lib/form/form-functions/form.functions.ts +++ b/src-ts/lib/form/form-functions/form.functions.ts @@ -33,9 +33,13 @@ export function initializeValues(inputs: Array, formValues?: inputs .filter(input => !input.dirty && !input.touched) .forEach(input => { - input.value = !!(formValues as any)?.hasOwnProperty(input.name) - ? (formValues as any)[input.name] - : undefined + if (input.type === 'checkbox') { + input.value = input.checked || false + } else { + input.value = !!(formValues as any)?.hasOwnProperty(input.name) + ? (formValues as any)[input.name] + : undefined + } }) } @@ -64,6 +68,7 @@ export async function onSubmitAsync( formValue: T, save: (value: T) => Promise, onSuccess?: () => void, + customValidateForm?: (formElements: HTMLFormControlsCollection, event: ValidationEvent, inputs: ReadonlyArray) => boolean ): Promise { event.preventDefault() @@ -80,7 +85,7 @@ export async function onSubmitAsync( // want to have it look like the submit succeeded const formValues: HTMLFormControlsCollection = (event.target as HTMLFormElement).elements if (action === 'submit') { - const isValid: boolean = validateForm(formValues, action, inputs) + const isValid: boolean = (customValidateForm || validateForm)(formValues, action, inputs) if (!isValid) { return Promise.reject() } @@ -115,13 +120,22 @@ function handleFieldEvent(input: HTMLInputElement | HTMLTextAreaElement, inpu const inputDef: FormInputModel = getInputModel(inputs, input.name) + const inputEl: HTMLInputElement = input as HTMLInputElement + if (event === 'change') { inputDef.dirty = input.value !== originalValue } inputDef.touched = true // set the def value - inputDef.value = input.value + if (input.type === 'checkbox') { + inputDef.value = inputEl.checked + inputDef.checked = inputEl.checked + } else if (input.type === 'file') { + inputDef.value = inputEl.files || undefined + } else { + inputDef.value = input.value + } // now let's validate the field const formElements: HTMLFormControlsCollection = (input.form as HTMLFormElement).elements @@ -171,7 +185,9 @@ function validateField(formInputDef: FormInputModel, formElements: HTMLFormContr }) } -export function validateForm(formElements: HTMLFormControlsCollection, event: 'blur' | 'change' | 'submit' | 'initial', inputs: ReadonlyArray): boolean { +export type ValidationEvent = 'blur' | 'change' | 'submit' | 'initial' + +export function validateForm(formElements: HTMLFormControlsCollection, event: ValidationEvent, inputs: ReadonlyArray): boolean { const errors: ReadonlyArray = inputs?.filter(formInputDef => { formInputDef.dirty = formInputDef.dirty || event === 'submit' validateField(formInputDef, formElements, event) diff --git a/src-ts/lib/form/form-group.model.ts b/src-ts/lib/form/form-group.model.ts index 7fe97df9f..7f90a5418 100644 --- a/src-ts/lib/form/form-group.model.ts +++ b/src-ts/lib/form/form-group.model.ts @@ -1,3 +1,5 @@ +import { CSSProperties } from 'react' + import { FormInputModel } from './form-input.model' export interface FormGroup { @@ -6,3 +8,8 @@ export interface FormGroup { readonly instructions?: string readonly title?: string } + +export interface FormGroupOptions { + groupWrapStyles?: CSSProperties + renderGroupDividers?: boolean +} diff --git a/src-ts/lib/form/form-groups/FormGroups.module.scss b/src-ts/lib/form/form-groups/FormGroups.module.scss index 6c3e7a4b0..3bf262416 100644 --- a/src-ts/lib/form/form-groups/FormGroups.module.scss +++ b/src-ts/lib/form/form-groups/FormGroups.module.scss @@ -1,5 +1,11 @@ +@import "../../styles/includes"; + .form-groups { display: grid; grid-template-columns: 1fr; justify-content: center; + + @include ltemd { + grid-template-columns: 1fr !important; + } } diff --git a/src-ts/lib/form/form-groups/FormGroups.tsx b/src-ts/lib/form/form-groups/FormGroups.tsx index 2562d0146..868c24af3 100644 --- a/src-ts/lib/form/form-groups/FormGroups.tsx +++ b/src-ts/lib/form/form-groups/FormGroups.tsx @@ -1,12 +1,13 @@ import { ChangeEvent, FocusEvent } from 'react' +import { PageDivider } from '../../page-divider' import { FormDefinition } from '../form-definition.model' import { FormGroup } from '../form-group.model' import { FormInputModel } from '../form-input.model' import { FormCardSet } from './form-card-set' import FormGroupItem from './form-group-item/FormGroupItem' -import { InputRating, InputText, InputTextarea } from './form-input' +import { InputImagePicker, InputRating, InputText, InputTextarea } from './form-input' import { FormInputRow } from './form-input-row' import { InputTextTypes } from './form-input/input-text/InputText' import FormRadio from './form-radio' @@ -23,15 +24,18 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr const { formDef, onBlur, onChange }: FormGroupsProps = props - const getTabIndex: (input: FormInputModel, index: number) => number = (input, index) => { + function getTabIndex(input: FormInputModel, index: number): number { const tabIndex: number = input.notTabbable ? -1 : index + 1 + (formDef.tabIndexStart || 0) return tabIndex } - const renderInputField: (input: FormInputModel, index: number) => JSX.Element | undefined = (input, index) => { + function renderInputField(input: FormInputModel, index: number): JSX.Element | undefined { + const tabIndex: number = getTabIndex(input, index) let inputElement: JSX.Element + + /* tslint:disable:cyclomatic-complexity */ switch (input.type) { case 'rating': @@ -40,11 +44,10 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr {...input} onChange={onChange} tabIndex={tabIndex} - value={input.value} + value={input.value as number | undefined} /> ) break - case 'textarea': inputElement = ( JSX.Element = (props: FormGroupsPr onBlur={onBlur} onChange={onChange} tabIndex={tabIndex} - value={input.value} + value={input.value as string | undefined} /> ) break case 'checkbox': + inputElement = ( + + ) + break case 'radio': inputElement = ( JSX.Element = (props: FormGroupsPr /> ) break + case 'image-picker': + inputElement = ( + + ) + break default: inputElement = ( JSX.Element = (props: FormGroupsPr onChange={onChange} tabIndex={tabIndex} type={input.type as InputTextTypes || 'text'} - value={input.value} + value={input.value as string | undefined} /> ) break @@ -100,14 +123,29 @@ const FormGroups: (props: FormGroupsProps) => JSX.Element = (props: FormGroupsPr ) } - const formGroups: Array = formDef?.groups?.map((element: FormGroup, index: number) => { - return - }) || [] + const formGroups: Array = formDef?.groups + ?.map((element: FormGroup, index: number) => { + return ( + + ) + }) + || [] return ( -
    - {formGroups} -
    + <> +
    + {formGroups} +
    + { + props.formDef.groupsOptions?.renderGroupDividers === false && + } + ) } diff --git a/src-ts/lib/form/form-groups/form-group-item/FormGroupItem.tsx b/src-ts/lib/form/form-groups/form-group-item/FormGroupItem.tsx index 41a568ec9..3b8840156 100644 --- a/src-ts/lib/form/form-groups/form-group-item/FormGroupItem.tsx +++ b/src-ts/lib/form/form-groups/form-group-item/FormGroupItem.tsx @@ -9,6 +9,7 @@ import styles from './FormGroupItem.module.scss' interface FormGroupItemProps { group: FormGroup + renderDividers?: boolean renderFormInput: (input: FormInputModel, index: number) => JSX.Element | undefined totalGroupCount: number } @@ -19,10 +20,11 @@ interface ItemRowProps { hasMultipleGroups: boolean, instructions?: string | undefined, isMultiFieldGroup: boolean, + renderDividers?: boolean title?: string, } -const TwoColumnItem: React.FC = ({ element, formInputs, hasMultipleGroups, instructions, isMultiFieldGroup, title }: ItemRowProps) => { +const TwoColumnItem: React.FC = ({ element, formInputs, hasMultipleGroups, instructions, isMultiFieldGroup, title, renderDividers }: ItemRowProps) => { return ( <>
    @@ -41,7 +43,9 @@ const TwoColumnItem: React.FC = ({ element, formInputs, hasMultipl {formInputs}
    - + { + renderDividers !== false && + } ) } @@ -67,7 +71,7 @@ const SingleColumnItem: React.FC = ({ formInputs, hasMultipleGroup ) } -const FormGroupItem: React.FC = ({ group, renderFormInput, totalGroupCount }: FormGroupItemProps) => { +const FormGroupItem: React.FC = ({ group, renderDividers, renderFormInput, totalGroupCount }: FormGroupItemProps) => { const { instructions, title, inputs, element }: FormGroup = group const formInputs: Array = inputs?.map((field: FormInputModel, index: number) => renderFormInput(field as FormInputModel, index)) || [] @@ -77,7 +81,7 @@ const FormGroupItem: React.FC = ({ group, renderFormInput, t return isCardSet ? : - + } export default FormGroupItem diff --git a/src-ts/lib/form/form-groups/form-input/index.ts b/src-ts/lib/form/form-groups/form-input/index.ts index 46f16f8c4..4e43914cb 100644 --- a/src-ts/lib/form/form-groups/form-input/index.ts +++ b/src-ts/lib/form/form-groups/form-input/index.ts @@ -1,3 +1,4 @@ +export * from './input-image-picker' export * from './form-input-autcomplete-option.enum' export * from './input-rating' export * from './input-select' diff --git a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.module.scss b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.module.scss new file mode 100644 index 000000000..5641206e7 --- /dev/null +++ b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.module.scss @@ -0,0 +1,40 @@ +@import "../../../../styles/includes"; +@import "../../../../styles/variables"; + +.filePicker { + display: flex; + align-items: center; + justify-content: center; + background-color: $black-5; + border-radius: 8px; + width: 132px; + height: 132px; + position: relative; + margin-bottom: $space-xl; + + @include ltemd { + width: 100%; + } + + .filePickerPlaceholder { + color: $turq-160; + text-align: center; + font-weight: $font-weight-bold; + } + + .filePickerPencil { + position: absolute; + top: 0; + right: 0; + color: $turq-160; + } + + .filePickerInput { + display: none; + } + + .badgeImage { + width: 72px; + height: 72px; + } +} \ No newline at end of file diff --git a/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx new file mode 100644 index 000000000..43882c44c --- /dev/null +++ b/src-ts/lib/form/form-groups/form-input/input-image-picker/InputImagePicker.tsx @@ -0,0 +1,74 @@ +import { ChangeEvent, createRef, Dispatch, FC, RefObject, SetStateAction, useEffect, useState } from 'react' + +import { Button, IconOutline } from '../../../../../lib' +import { InputValue } from '../../../form-input.model' + +import styles from './InputImagePicker.module.scss' + +interface InputImagePickerProps { + readonly fileConfig?: { + readonly acceptFileType?: string + readonly maxFileSize?: number + } + readonly name: string + readonly onChange: (event: ChangeEvent) => void + readonly value?: InputValue +} + +const InputImagePicker: FC = (props: InputImagePickerProps) => { + + const fileInputRef: RefObject = createRef() + + // tslint:disable-next-line:no-null-keyword + const [files, setFiles]: [FileList | null, Dispatch>] = useState(null) + const [fileDataURL, setFileDataURL]: [string | undefined, Dispatch>] = useState() + + useEffect(() => { + if (files && files.length) { + const fileReader: FileReader = new FileReader() + fileReader.onload = e => { + const { result }: any = e.target + if (result) { + setFileDataURL(result) + } + } + fileReader.readAsDataURL(files[0]) + } else if (fileDataURL) { + setFileDataURL(undefined) + } + }, [ + files, + fileDataURL, + ]) + + return ( +
    +
    + ) +} + +export default InputImagePicker diff --git a/src-ts/lib/form/form-groups/form-input/input-image-picker/index.ts b/src-ts/lib/form/form-groups/form-input/input-image-picker/index.ts new file mode 100644 index 000000000..ceeb9aa9d --- /dev/null +++ b/src-ts/lib/form/form-groups/form-input/input-image-picker/index.ts @@ -0,0 +1 @@ +export { default as InputImagePicker } from './InputImagePicker' diff --git a/src-ts/lib/form/form-groups/form-input/input-text/InputText.module.scss b/src-ts/lib/form/form-groups/form-input/input-text/InputText.module.scss index 7b47d447d..ac7f33476 100644 --- a/src-ts/lib/form/form-groups/form-input/input-text/InputText.module.scss +++ b/src-ts/lib/form/form-groups/form-input/input-text/InputText.module.scss @@ -30,9 +30,32 @@ } &.checkbox { - & { - width: 20px; - height: 20px; + @extend .body-small; + color: $black-60; + box-sizing: border-box; + border: 0; + width: 100%; + padding: 0; + margin: 0; + height: auto; + border-radius: 0; + + &:focus { + box-shadow: none; + border: none; + outline: none; + color: $black-100; + } + + &:disabled { + background-color: $black-10; + } + + &.checkbox { + & { + width: 20px; + height: 20px; + } } } } diff --git a/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx b/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx index 21e94e307..a88c96fb5 100644 --- a/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx @@ -1,6 +1,7 @@ import cn from 'classnames' import { FC, FocusEvent } from 'react' +import { InputValue } from '../../../form-input.model' import { FormInputAutocompleteOption } from '../form-input-autcomplete-option.enum' import { InputWrapper } from '../input-wrapper' @@ -8,8 +9,9 @@ import styles from './InputText.module.scss' export type InputTextTypes = 'checkbox' | 'password' | 'text' -interface InputTextProps { +export interface InputTextProps { readonly autocomplete?: FormInputAutocompleteOption + readonly checked?: boolean readonly className?: string readonly dirty?: boolean readonly disabled?: boolean @@ -24,11 +26,15 @@ interface InputTextProps { readonly spellCheck?: boolean readonly tabIndex: number readonly type: InputTextTypes - readonly value?: string | number + readonly value?: InputValue } const InputText: FC = (props: InputTextProps) => { + const defaultValue: string | number | undefined = props.type === 'checkbox' && !!props.checked + ? 'on' + : props.value as string | number | undefined + return ( = (props: InputTextProps) => { > + checked?: boolean readonly className?: string readonly dependentFields?: Array dirty?: boolean disabled?: boolean error?: string readonly events?: ReadonlyArray + readonly fileConfig?: { + readonly acceptFileType?: string + readonly maxFileSize?: number + } + readonly files?: FileList readonly hideInlineErrors?: boolean readonly hint?: string readonly id?: string @@ -38,7 +46,7 @@ export interface FormInputModel { readonly spellCheck?: boolean readonly title?: string touched?: boolean - readonly type: 'card-set' | 'checkbox' | 'password' | 'radio' | 'rating' | 'text' | 'textarea' + readonly type: 'card-set' | 'checkbox' | 'password' | 'radio' | 'rating' | 'text' | 'textarea' | 'image-picker' readonly validators?: ReadonlyArray - value?: string + value?: InputValue } diff --git a/src-ts/lib/form/validator-functions/validator.functions.ts b/src-ts/lib/form/validator-functions/validator.functions.ts index af1c9ad7a..9dc9663f9 100644 --- a/src-ts/lib/form/validator-functions/validator.functions.ts +++ b/src-ts/lib/form/validator-functions/validator.functions.ts @@ -1,12 +1,13 @@ import { formGetInput } from '../form-functions' +import { InputValue } from '../form-input.model' -function checkForBooleanValueAndThrowError(value: string | boolean | undefined): void { +function checkForBooleanValueAndThrowError(value: InputValue): void { if (typeof value === 'boolean') { throw new Error(`The value for the email validator cannot be a boolean`) } } -export function doesNotMatchOther(value: string | boolean | undefined, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { +export function doesNotMatchOther(value: InputValue, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { checkForBooleanValueAndThrowError(value) @@ -27,7 +28,7 @@ export function doesNotMatchOther(value: string | boolean | undefined, formEleme return `Cannot match the ${getOtherFieldLabel(otherField, otherFieldName)} value` } -export function email(value: string | boolean | undefined): string | undefined { +export function email(value: InputValue): string | undefined { checkForBooleanValueAndThrowError(value) @@ -42,10 +43,10 @@ export function email(value: string | boolean | undefined): string | undefined { const emailRegex: RegExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ - return !emailRegex.test(value) ? 'Invalid email' : undefined + return !emailRegex.test(value as string) ? 'Invalid email' : undefined } -export function password(value: string | boolean | undefined): string | undefined { +export function password(value: InputValue): string | undefined { checkForBooleanValueAndThrowError(value) @@ -64,10 +65,10 @@ export function password(value: string | boolean | undefined): string | undefine // - at least 1 symbol or number const passwordRegex: RegExp = /^(?=.*[a-zA-Z])(?=.*[#$^+=!*()@%&\d]).{8,}$/g - return !passwordRegex.test(value) ? 'Password rules: 8+ characters, 1+ letter, and 1+ number or symbol' : undefined + return !passwordRegex.test(value as string) ? 'Password rules: 8+ characters, 1+ letter, and 1+ number or symbol' : undefined } -export function matchOther(value: string | boolean | undefined, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { +export function matchOther(value: InputValue, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { checkForBooleanValueAndThrowError(value) @@ -88,11 +89,11 @@ export function matchOther(value: string | boolean | undefined, formElements?: H return `Does not match the ${getOtherFieldLabel(otherField, otherFieldName)}` } -export function required(value: string | boolean | undefined): string | undefined { +export function required(value: InputValue): string | undefined { return (value === undefined || value === '') ? 'Required' : undefined } -export function requiredIfOther(value: string | boolean | undefined, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { +export function requiredIfOther(value: InputValue, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { // if there is a value, there's no need to check the other input if (typeof value === 'string' && !!value) { @@ -129,7 +130,7 @@ export function sslUrl(value: string | undefined): string | undefined { export interface ValidatorFn { dependentField?: string, - validator: (value: string | boolean | undefined, formValues?: HTMLFormControlsCollection, otherField?: string) => string | undefined + validator: (value: InputValue, formValues?: HTMLFormControlsCollection, otherField?: string) => string | undefined } function getOtherField(formElements?: HTMLFormControlsCollection, otherFieldName?: string): HTMLInputElement { diff --git a/src-ts/lib/table/Table.tsx b/src-ts/lib/table/Table.tsx index ad3443d25..c9fd0a2f7 100644 --- a/src-ts/lib/table/Table.tsx +++ b/src-ts/lib/table/Table.tsx @@ -61,6 +61,7 @@ const Table: (props: TableProps) = columns, data, defaultSortDirectionMap, + props.onToggleSort, sort, ]) diff --git a/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts b/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts index c1b0124fe..c397769c1 100644 --- a/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts +++ b/src-ts/tools/gamification-admin/game-config/gamification-config.model.ts @@ -1,4 +1,6 @@ export interface GamificationConfigModel { + ACCEPTED_BADGE_MIME_TYPES: string + MAX_BADGE_IMAGE_FILE_SIZE: number ORG_ID: string PAGE_SIZE: number } diff --git a/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts b/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts index b2c40e4d8..b35ada502 100644 --- a/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts +++ b/src-ts/tools/gamification-admin/game-config/gamification.default.config.ts @@ -1,6 +1,8 @@ import { GamificationConfigModel } from './gamification-config.model' export const GamificationConfigDefault: GamificationConfigModel = { + ACCEPTED_BADGE_MIME_TYPES: 'image/svg+xml,image/svg', + MAX_BADGE_IMAGE_FILE_SIZE: 5000000, // 5mb in bytes ORG_ID: '6052dd9b-ea80-494b-b258-edd1331e27a3', PAGE_SIZE: 12, } diff --git a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss new file mode 100644 index 000000000..adadea7c1 --- /dev/null +++ b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss @@ -0,0 +1,39 @@ +@import "../../../../../lib/styles/variables"; + +.wrapper { + display: flex; + flex-direction: column; + + .badge { + display: flex; + align-items: center; + margin-bottom: $space-xxl; + + .badge-image { + width: 43px; + height: 43px; + margin-right: $space-xl; + } + + .badge-image-disabled { + width: 43px; + height: 43px; + margin-right: $space-xl; + opacity: 0.5; + filter: grayscale(1); + } + + .badge-name { + font-size: 16px; + } + } + + .actions { + display: flex; + align-items: center; + + a { + margin-right: $space-md; + } + } +} \ No newline at end of file diff --git a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx new file mode 100644 index 000000000..e6a27a8c4 --- /dev/null +++ b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx @@ -0,0 +1,53 @@ +import { FC } from 'react' + +import { BaseModal, Button } from '../../../../../lib' +import { badgeDetailPath } from '../../../gamification-admin.routes' +import { GameBadge } from '../../game-badge.model' + +import styles from './BadgeCreatedModal.module.scss' +export interface BadgeCreatedModalProps { + badge: GameBadge + isOpen: boolean + onClose: () => void +} + +const BadgeCreatedModal: FC = (props: BadgeCreatedModalProps) => { + + function onClose(): void { + props.onClose() + } + + return ( + +
    +
    + {props.badge.badge_name} +

    {props.badge.badge_name} badge has been sucessfully created.

    +
    +
    +
    +
    +
    + ) +} + +export default BadgeCreatedModal diff --git a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/index.ts b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/index.ts new file mode 100644 index 000000000..2e8ce2cda --- /dev/null +++ b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/index.ts @@ -0,0 +1 @@ +export { default as BadgeCreatedModal } from './BadgeCreatedModal' diff --git a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx index 108d2cf53..ee01f39d4 100644 --- a/src-ts/tools/gamification-admin/gamification-admin.routes.tsx +++ b/src-ts/tools/gamification-admin/gamification-admin.routes.tsx @@ -5,8 +5,8 @@ import BadgeDetailPage from './pages/badge-detail/BadgeDetailPage' import BadgeListingPage from './pages/badge-listing/BadgeListingPage' import CreateBadgePage from './pages/create-badge/CreateBadgePage' -const baseDetailPath: string = '/badge-detail' -const createBadgePath: string = '/create-badge' +export const baseDetailPath: string = '/badge-detail' +export const createBadgePath: string = '/create-badge' export const basePath: string = '/gamification-admin' diff --git a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss index 7da13b030..fac1cbcbc 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss +++ b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.module.scss @@ -1,17 +1,6 @@ -.contentLayout { - width: 100%; - padding-bottom: 0; - - .contentLayout-outer { - width: 100%; - - .contentLayout-inner { - width: 100%; - overflow: visible; - } - } -} +@import "../../../../lib/styles/variables"; .container { display: flex; + padding-top: $space-xxxxl; } diff --git a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx index 22d7e59d6..9d34a510c 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/CreateBadgePage.tsx @@ -1,8 +1,10 @@ -import { FC } from 'react' +import { Dispatch, FC, SetStateAction, useState } from 'react' import { Breadcrumb, BreadcrumbItemModel, ContentLayout } from '../../../../lib' -import { useGamificationBreadcrumb } from '../../game-lib' +import { GameBadge, useGamificationBreadcrumb } from '../../game-lib' +import { BadgeCreatedModal } from '../../game-lib/modals/badge-created-modal' +import { CreateBadgeForm, createBadgeFormDef } from './create-badge-form' import styles from './CreateBadgePage.module.scss' const CreateBadgePage: FC = () => { @@ -14,17 +16,35 @@ const CreateBadgePage: FC = () => { }, ]) + const [showBadgeCreatedModal, setShowBadgeCreatedModal]: [boolean, Dispatch>] + = useState(false) + + const [createdBadge, setCreatedBadge]: [GameBadge | undefined, Dispatch>] + = useState() + + function onSave(newBadge: GameBadge): void { + setCreatedBadge(newBadge) + setShowBadgeCreatedModal(true) + } + return (
    - +
    + { + createdBadge && setShowBadgeCreatedModal(false)} + /> + }
    ) } diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx new file mode 100644 index 000000000..558132e61 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx @@ -0,0 +1,50 @@ +import { FC } from 'react' + +import { Form, FormDefinition, formGetInputModel, FormInputModel } from '../../../../../lib' +import { GamificationConfig } from '../../../game-config' +import { GameBadge } from '../../../game-lib' + +import { CreateBadgeFormField } from './create-badge-form.config' +import { CreateBadgeRequest } from './create-badge-functions' +import { createBadgeSubmitRequestAsync } from './create-badge-functions/create-badge-store' + +export interface CreateBadgeFormProps { + formDef: FormDefinition + onSave: (createdBadge: GameBadge) => void +} + +const CreateBadgeForm: FC = (props: CreateBadgeFormProps) => { + + function generateRequest(inputs: ReadonlyArray): CreateBadgeRequest { + const badgeName: string = formGetInputModel(inputs, CreateBadgeFormField.badgeName).value as string + const badgeDesc: string = formGetInputModel(inputs, CreateBadgeFormField.badgeDesc).value as string + const badgeActive: boolean = formGetInputModel(inputs, CreateBadgeFormField.badgeActive).value as boolean + const files: FileList = formGetInputModel(inputs, CreateBadgeFormField.file).value as FileList + + return { + badgeActive, + badgeDesc, + badgeName, + badgeStatus: 'Active', // not used currently thus fixed field + files, + orgID: GamificationConfig.ORG_ID, + } + } + + async function saveAsync(request: CreateBadgeRequest): Promise { + return createBadgeSubmitRequestAsync(request) + .then((createdBadge: GameBadge) => { + props.onSave(createdBadge) + }) + } + + return ( + + ) +} + +export default CreateBadgeForm diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx new file mode 100644 index 000000000..74a0414b9 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx @@ -0,0 +1,87 @@ +import { FormDefinition, IconOutline, validatorRequired } from '../../../../../lib' +import { GamificationConfig } from '../../../game-config' + +export enum CreateBadgeFormField { + badgeActive = 'badgeActive', + badgeName = 'badgeName', + badgeDesc = 'badgeDesc', + file = 'file', +} + +export const createBadgeFormDef: FormDefinition = { + buttons: { + primaryGroup: [ + { + buttonStyle: 'primary', + isSubmit: true, + label: 'Save Badge', + onClick: (e) => { }, + size: 'lg', + type: 'submit', + }, + ], + secondaryGroup: [ + { + buttonStyle: 'icon-bordered', + icon: IconOutline.ChevronLeftIcon, + route: '/gamification-admin', + size: 'lg', + }, + ], + }, + groups: [ + { + inputs: [ + { + fileConfig: { + acceptFileType: GamificationConfig.ACCEPTED_BADGE_MIME_TYPES, + maxFileSize: GamificationConfig.MAX_BADGE_IMAGE_FILE_SIZE, + }, + name: CreateBadgeFormField.file, + type: 'image-picker', + validators: [ + { + validator: validatorRequired, + }, + ], + }, + ], + }, + { + inputs: [ + { + label: 'Badge Name', + name: CreateBadgeFormField.badgeName, + type: 'text', + validators: [ + { + validator: validatorRequired, + }, + ], + }, + { + label: 'Badge Description', + name: CreateBadgeFormField.badgeDesc, + type: 'textarea', + validators: [ + { + validator: validatorRequired, + }, + ], + }, + { + checked: true, + label: 'Activate Badge', + name: CreateBadgeFormField.badgeActive, + type: 'checkbox', + }, + ], + }, + ], + groupsOptions: { + groupWrapStyles: { + gridTemplateColumns: '160px 1fr', + }, + renderGroupDividers: false, + }, +} diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts new file mode 100644 index 000000000..a0ed3c677 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge-request.model.ts @@ -0,0 +1,8 @@ +export interface CreateBadgeRequest { + badgeActive: boolean + badgeDesc: string + badgeName: string + badgeStatus: string + files: FileList + orgID: string +} diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts new file mode 100644 index 000000000..9a2f83fba --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/create-badge.store.ts @@ -0,0 +1,21 @@ +import { EnvironmentConfig } from '../../../../../../../config' +import { xhrPostAsync } from '../../../../../../../lib' +import { GameBadge } from '../../../../../game-lib' + +import { CreateBadgeRequest } from './create-badge-request.model' + +export async function submitRequestAsync(request: CreateBadgeRequest): Promise { + const url: string = `${EnvironmentConfig.API.V5}/gamification/badges` + + const form: any = new FormData() + + // fill the form + form.append('file', request.files[0]) + form.append('organization_id', request.orgID) + form.append('badge_status', request.badgeStatus) + form.append('badge_name', request.badgeName) + form.append('badge_description', request.badgeDesc) + form.append('active', request.badgeActive ? 'true' : 'false') + + return xhrPostAsync(url, form) +} diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/index.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/index.ts new file mode 100644 index 000000000..5f0511634 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge-store/index.ts @@ -0,0 +1,2 @@ +export * from './create-badge-request.model' +export { submitRequestAsync as createBadgeSubmitRequestAsync } from './create-badge.store' diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge.functions.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge.functions.ts new file mode 100644 index 000000000..ddce9a486 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/create-badge.functions.ts @@ -0,0 +1,5 @@ +import { CreateBadgeRequest, createBadgeSubmitRequestAsync } from './create-badge-store' + +export async function submitRequestAsync(request: CreateBadgeRequest): Promise { + return createBadgeSubmitRequestAsync(request) +} diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/index.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/index.ts new file mode 100644 index 000000000..82368d47a --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-functions/index.ts @@ -0,0 +1,2 @@ +export { type CreateBadgeRequest } from './create-badge-store' +export { submitRequestAsync as contactSupportSubmitRequestAsync } from './create-badge.functions' diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/index.ts b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/index.ts new file mode 100644 index 000000000..a323b7903 --- /dev/null +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/index.ts @@ -0,0 +1,2 @@ +export { default as CreateBadgeForm } from './CreateBadgeForm' +export { createBadgeFormDef } from './create-badge-form.config' From a109309b4173d469456ff79fd01f15c1a9409c12 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 27 Sep 2022 16:14:21 +0300 Subject: [PATCH 075/113] TCA-465 - text misaligned in the Course details screen. --- .../collapsible-item/CollapsibleItem.module.scss | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.module.scss b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.module.scss index a5ed5f680..72a907275 100644 --- a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.module.scss +++ b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.module.scss @@ -57,7 +57,7 @@ @include icon-xxl; } - + .wrap:global(.collapsed) & svg { transform: rotate(180deg); } @@ -75,13 +75,17 @@ .short-desc { margin-top: $space-sm; + + > pre { + display: inline; + } } .summary { display: flex; color: $tc-white; margin-top: $border-xs; - + > * + * { &:before { content: ""; From 11e7a8c13689496596558b57656cc4635404436e Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 27 Sep 2022 17:32:50 +0300 Subject: [PATCH 076/113] TCA-463 - show "various" as time estimate when estimated time is 0 --- .../collapsible-item/CollapsibleItem.tsx | 10 ++++++---- .../learn-lib/curriculum-summary/CurriculumSummary.tsx | 6 ++++-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx index 5380318c8..1af065230 100644 --- a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx +++ b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.tsx @@ -106,10 +106,12 @@ const CollapsibleItem: FC = (props: CollapsibleItemProps) {props.lessonsCount} Lessons - - - {props.duration.value} {props.duration.units} - + {props.duration.value !== 0 && ( + + + {props.duration.value} {props.duration.units} + + )}
    ') }}> diff --git a/src-ts/tools/learn/learn-lib/curriculum-summary/CurriculumSummary.tsx b/src-ts/tools/learn/learn-lib/curriculum-summary/CurriculumSummary.tsx index 489a677a8..0e8492ebc 100644 --- a/src-ts/tools/learn/learn-lib/curriculum-summary/CurriculumSummary.tsx +++ b/src-ts/tools/learn/learn-lib/curriculum-summary/CurriculumSummary.tsx @@ -11,6 +11,8 @@ interface CurriculumSummaryProps { } const CurriculumSummary: FC = (props: CurriculumSummaryProps) => { + const hasTimeEstimate: boolean = props.completionHours?.value !== 0 + return (
    @@ -32,10 +34,10 @@ const CurriculumSummary: FC = (props: CurriculumSummaryP

    - {props.completionHours?.value ?? 0} + {hasTimeEstimate ? props.completionHours?.value : (<> )}

    - {props.completionHours?.units ?? 'Hours'} + {hasTimeEstimate ? (props.completionHours?.units ?? 'Hours') : 'Various'}
    From 21c3ad4b76ebd494514fa963bbae6accfbe12b87 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 27 Sep 2022 17:32:56 +0300 Subject: [PATCH 077/113] lint fixes --- .../learn/free-code-camp/FreeCodeCamp.tsx | 1 + src-ts/tools/learn/learn.routes.tsx | 20 +++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx b/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx index 11268128f..83af1a695 100644 --- a/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx +++ b/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx @@ -291,6 +291,7 @@ const FreeCodeCamp: FC<{}> = () => { certificationParam, navigate, providerParam, + profile?.handle, setCertificateProgress, ]) diff --git a/src-ts/tools/learn/learn.routes.tsx b/src-ts/tools/learn/learn.routes.tsx index f3d415044..c5c3548ed 100644 --- a/src-ts/tools/learn/learn.routes.tsx +++ b/src-ts/tools/learn/learn.routes.tsx @@ -9,6 +9,16 @@ import { LearnConfig } from './learn-config' import { MyLearning } from './my-learning' import { WelcomePage } from './welcome' +export enum LEARN_PATHS { + certificate = '/certificate', + completed = '/learn/completed', + myCertificate = '/learn/my-certificate', + myLearning = '/learn/my-learning', + fcc = '/learn/fcc', + root = '/learn', + startCourseRouteFlag = 'start-course', +} + export function getAuthenticateAndStartCourseRoute(): string { return `${authUrlLogin()}${encodeURIComponent(`?${LEARN_PATHS.startCourseRouteFlag}`)}` } @@ -53,16 +63,6 @@ export function getUserCertificateUrl(provider: string, certification: string, h return `${window.location.origin}${getCoursePath(provider, certification)}/${handle}${LEARN_PATHS.certificate}` } -export enum LEARN_PATHS { - certificate = '/certificate', - completed = '/learn/completed', - myCertificate = '/learn/my-certificate', - myLearning = '/learn/my-learning', - fcc = '/learn/fcc', - root = '/learn', - startCourseRouteFlag = 'start-course', -} - export const rootRoute: string = LEARN_PATHS.root export const absoluteRootRoute: string = `${window.location.origin}${LEARN_PATHS.root}` From d3d5efd85f05da24b8a0e304b956c3851df4dd87 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 28 Sep 2022 10:56:19 +0300 Subject: [PATCH 078/113] TCA-463 - update label for variable duration on courses --- .../learn/learn-lib/curriculum-summary/CurriculumSummary.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-ts/tools/learn/learn-lib/curriculum-summary/CurriculumSummary.tsx b/src-ts/tools/learn/learn-lib/curriculum-summary/CurriculumSummary.tsx index 0e8492ebc..071ce6e86 100644 --- a/src-ts/tools/learn/learn-lib/curriculum-summary/CurriculumSummary.tsx +++ b/src-ts/tools/learn/learn-lib/curriculum-summary/CurriculumSummary.tsx @@ -37,7 +37,7 @@ const CurriculumSummary: FC = (props: CurriculumSummaryP {hasTimeEstimate ? props.completionHours?.value : (<> )}
    - {hasTimeEstimate ? (props.completionHours?.units ?? 'Hours') : 'Various'} + {hasTimeEstimate ? (props.completionHours?.units ?? 'Hours') : 'Times vary'}
    From 307ed4c8178e2c90117c879ec6e754b173e27cbd Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Wed, 28 Sep 2022 11:54:31 +0300 Subject: [PATCH 079/113] TCA-465 - text misaligned in the Course details screen. --- .../course-outline/collapsible-item/CollapsibleItem.module.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.module.scss b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.module.scss index 72a907275..04597ce09 100644 --- a/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.module.scss +++ b/src-ts/tools/learn/learn-lib/course-outline/collapsible-item/CollapsibleItem.module.scss @@ -76,7 +76,7 @@ .short-desc { margin-top: $space-sm; - > pre { + pre { display: inline; } } From f195175e04d1b2d84c98542b0b5297e784794040 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Wed, 28 Sep 2022 22:07:30 +0300 Subject: [PATCH 080/113] Fix GAME-141 --- .../create-badge/create-badge-form/create-badge-form.config.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx index 74a0414b9..6621857d8 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/create-badge-form.config.tsx @@ -52,6 +52,7 @@ export const createBadgeFormDef: FormDefinition = { { label: 'Badge Name', name: CreateBadgeFormField.badgeName, + placeholder: 'Enter badge name', type: 'text', validators: [ { @@ -62,6 +63,7 @@ export const createBadgeFormDef: FormDefinition = { { label: 'Badge Description', name: CreateBadgeFormField.badgeDesc, + placeholder: 'Enter badge description, details, how to get awarded info', type: 'textarea', validators: [ { From 6c8cc3deff0eecdafcd8cb17b6047ec4061a95ad Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 29 Sep 2022 12:10:25 +0300 Subject: [PATCH 081/113] TCA-470 - update sort, use publishedAt --- .../learn-certification.model.ts | 1 + .../all-certifications.provider.tsx | 6 +++++- src-ts/tools/learn/learn-lib/functions/learn.factory.ts | 4 ++++ src-ts/tools/learn/welcome/WelcomePage.tsx | 4 ++-- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-functions/learn-certification.model.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-functions/learn-certification.model.ts index 9fecdd7dd..f592b7c85 100644 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-functions/learn-certification.model.ts +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-functions/learn-certification.model.ts @@ -11,6 +11,7 @@ export interface LearnCertification extends LearnModelBase { key: string providerCrertificationId: string providerName: string + publishedAt?: Date state: 'active' | 'coming-soon' title: string trackType: LearnCertificateTrackType diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx index 65ace83d9..f5aee7524 100644 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx @@ -42,7 +42,11 @@ export function useAllCertifications( ): Array { return !sort.current ? certificates - : orderBy([...certificates], sort.current.field, sort.current.direction) + : orderBy( + [...certificates], + [sort.current.field, 'title'], // always second sort by title + [sort.current.direction, 'asc'] + ) } function getFilteredCertifications( diff --git a/src-ts/tools/learn/learn-lib/functions/learn.factory.ts b/src-ts/tools/learn/learn-lib/functions/learn.factory.ts index 8d9d19830..ead0707c1 100644 --- a/src-ts/tools/learn/learn-lib/functions/learn.factory.ts +++ b/src-ts/tools/learn/learn-lib/functions/learn.factory.ts @@ -1,6 +1,7 @@ interface LearnResponseModel { createdAt?: string | Date updatedAt?: string | Date + publishedAt?: string | Date } export function create(item: T): T { @@ -11,6 +12,9 @@ export function create(item: T): T { if (typeof item?.updatedAt === 'string') { item.updatedAt = new Date(item.updatedAt) } + if (typeof item?.publishedAt === 'string') { + item.publishedAt = new Date(item.publishedAt) + } return item } diff --git a/src-ts/tools/learn/welcome/WelcomePage.tsx b/src-ts/tools/learn/welcome/WelcomePage.tsx index c4d0be442..f9aa1d7b3 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.tsx +++ b/src-ts/tools/learn/welcome/WelcomePage.tsx @@ -20,8 +20,8 @@ import styles from './WelcomePage.module.scss' type SORT_FIELD_TYPE = keyof LearnCertification const SORT_OPTIONS: Array<{label: string, value: SORT_FIELD_TYPE}> = [ + {label: 'Newest Course', value: 'publishedAt'}, {label: 'Category', value: 'category'}, - {label: 'Newest', value: 'createdAt'}, {label: 'Title', value: 'title'}, ] export const DEFAULT_SORT: SORT_FIELD_TYPE = SORT_OPTIONS[0].value @@ -47,7 +47,7 @@ const WelcomePage: FC<{}> = () => { value: selectedCategory, }, sort: { - direction: sortField === 'createdAt' ? 'desc' : 'asc', + direction: sortField === 'publishedAt' ? 'desc' : 'asc', field: sortField, }, } From d69583dd7244731025e9ae59e9fbfe496a4fd9b2 Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Thu, 29 Sep 2022 12:11:21 +0300 Subject: [PATCH 082/113] Badge create page UI fixes from QA --- src-ts/lib/form/Form.tsx | 9 ++++++ .../input-wrapper/InputWrapper.module.scss | 3 -- .../validator.functions.ts | 2 +- .../BadgeCreatedModal.module.scss | 20 +++++++++--- .../badge-created-modal/BadgeCreatedModal.tsx | 31 ++++++++++++------- .../create-badge-form/CreateBadgeForm.tsx | 1 + 6 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src-ts/lib/form/Form.tsx b/src-ts/lib/form/Form.tsx index c1aa827d1..ad7d7cb14 100644 --- a/src-ts/lib/form/Form.tsx +++ b/src-ts/lib/form/Form.tsx @@ -35,6 +35,7 @@ interface FormProps { readonly onChange?: (inputs: ReadonlyArray) => void, readonly onSuccess?: () => void readonly requestGenerator: (inputs: ReadonlyArray) => RequestType + readonly resetFormOnUnmount?: boolean readonly save: (value: RequestType) => Promise } @@ -83,6 +84,14 @@ const Form: (props: FormProps { + return () => { + if (props.resetFormOnUnmount) { + onReset() + } + } + }, []) + function checkIfFormIsValid(formInputFields: Array): void { setFormInvalid(formInputFields.filter(item => !!item.error).length > 0) } diff --git a/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.module.scss b/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.module.scss index 4052802ad..bb0a843be 100644 --- a/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.module.scss +++ b/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.module.scss @@ -45,9 +45,6 @@ $error-line-height: 14px; display: flex; flex-direction: row; align-items: center; - @include ltemd { - align-items: flex-start; - } .checkbox-label { margin-left: $space-sm; flex: 1; diff --git a/src-ts/lib/form/validator-functions/validator.functions.ts b/src-ts/lib/form/validator-functions/validator.functions.ts index 9dc9663f9..dbf89559a 100644 --- a/src-ts/lib/form/validator-functions/validator.functions.ts +++ b/src-ts/lib/form/validator-functions/validator.functions.ts @@ -90,7 +90,7 @@ export function matchOther(value: InputValue, formElements?: HTMLFormControlsCol } export function required(value: InputValue): string | undefined { - return (value === undefined || value === '') ? 'Required' : undefined + return (value === undefined || value === '' || !(value as FileList).length) ? 'Required' : undefined } export function requiredIfOther(value: InputValue, formElements?: HTMLFormControlsCollection, otherFieldName?: string): string | undefined { diff --git a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss index adadea7c1..4ba71f360 100644 --- a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss +++ b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.module.scss @@ -1,8 +1,11 @@ @import "../../../../../lib/styles/variables"; +@import "../../../../../lib/styles/includes"; .wrapper { display: flex; flex-direction: column; + justify-content: space-between; + min-height: 100%; .badge { display: flex; @@ -28,12 +31,21 @@ } } - .actions { + .actions-wrap { display: flex; - align-items: center; + flex-direction: column; + + .actions { + display: flex; + align-items: center; + + @include ltemd { + justify-content: flex-end; + } - a { - margin-right: $space-md; + a { + margin-right: $space-md; + } } } } \ No newline at end of file diff --git a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx index 375c47192..a46076c18 100644 --- a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx +++ b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' -import { BaseModal, Button } from '../../../../../lib' +import { BaseModal, Button, PageDivider, useCheckIsMobile } from '../../../../../lib' import { badgeDetailPath } from '../../../gamification-admin.routes' import { GameBadge } from '../../game-badge.model' @@ -13,6 +13,8 @@ export interface BadgeCreatedModalProps { const BadgeCreatedModal: FC = (props: BadgeCreatedModalProps) => { + const isMobile: boolean = useCheckIsMobile() + function onClose(): void { props.onClose() } @@ -34,17 +36,22 @@ const BadgeCreatedModal: FC = (props: BadgeCreatedModalP />

    {props.badge.badge_name} badge has been sucessfully created.

    -
    -
    diff --git a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx index 558132e61..d32e0501b 100644 --- a/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx +++ b/src-ts/tools/gamification-admin/pages/create-badge/create-badge-form/CreateBadgeForm.tsx @@ -42,6 +42,7 @@ const CreateBadgeForm: FC = (props: CreateBadgeFormProps) ) From fdc6c31c03ee0919432d104717ab7e5228e0d720 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Thu, 29 Sep 2022 12:22:15 +0300 Subject: [PATCH 083/113] lint fix --- src-ts/tools/learn/learn-lib/functions/learn.factory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-ts/tools/learn/learn-lib/functions/learn.factory.ts b/src-ts/tools/learn/learn-lib/functions/learn.factory.ts index ead0707c1..308d13da2 100644 --- a/src-ts/tools/learn/learn-lib/functions/learn.factory.ts +++ b/src-ts/tools/learn/learn-lib/functions/learn.factory.ts @@ -1,7 +1,7 @@ interface LearnResponseModel { createdAt?: string | Date - updatedAt?: string | Date publishedAt?: string | Date + updatedAt?: string | Date } export function create(item: T): T { From 0b761d5801c41eea9dda130067134fa07a76768e Mon Sep 17 00:00:00 2001 From: Kiril Kartunov Date: Fri, 30 Sep 2022 12:09:21 +0300 Subject: [PATCH 084/113] QA fixes badge create page GAME-82 --- src-ts/lib/form/Form.tsx | 14 ++++++++++---- .../input-textarea/InputTextarea.module.scss | 8 +++++++- .../badge-created-modal/BadgeCreatedModal.tsx | 2 +- .../create-badge-form/CreateBadgeForm.tsx | 1 + 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src-ts/lib/form/Form.tsx b/src-ts/lib/form/Form.tsx index ad7d7cb14..08bbc90c2 100644 --- a/src-ts/lib/form/Form.tsx +++ b/src-ts/lib/form/Form.tsx @@ -35,6 +35,7 @@ interface FormProps { readonly onChange?: (inputs: ReadonlyArray) => void, readonly onSuccess?: () => void readonly requestGenerator: (inputs: ReadonlyArray) => RequestType + readonly resetFormAfterSave?: boolean readonly resetFormOnUnmount?: boolean readonly save: (value: RequestType) => Promise } @@ -120,16 +121,21 @@ const Form: (props: FormProps): Promise { const values: RequestType = props.requestGenerator(inputs) formOnSubmitAsync(props.action || 'submit', event, formDef, values, props.save, props.onSuccess) .then(() => { - setFormKey(Date.now()) - formOnReset(inputs, props.formValues) - setFormDef({ ...formDef }) - setInputs(formGetInputFields(formDef.groups || [])) + if (!props.resetFormAfterSave) { + setFormKey(Date.now()) + formOnReset(inputs, props.formValues) + setFormDef({ ...formDef }) + setInputs(formGetInputFields(formDef.groups || [])) + } else { + onReset() + } }) .catch((error: string | undefined) => { setFormError(error) diff --git a/src-ts/lib/form/form-groups/form-input/input-textarea/InputTextarea.module.scss b/src-ts/lib/form/form-groups/form-input/input-textarea/InputTextarea.module.scss index e31b617c1..1966aed7f 100644 --- a/src-ts/lib/form/form-groups/form-input/input-textarea/InputTextarea.module.scss +++ b/src-ts/lib/form/form-groups/form-input/input-textarea/InputTextarea.module.scss @@ -12,4 +12,10 @@ margin-left: calc(-1 * $border); overflow: hidden; padding: $border; -} + + &::placeholder { + color: $black-60; + opacity: 1; + text-transform: none; + } +} \ No newline at end of file diff --git a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx index a46076c18..d9009375a 100644 --- a/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx +++ b/src-ts/tools/gamification-admin/game-lib/modals/badge-created-modal/BadgeCreatedModal.tsx @@ -49,7 +49,7 @@ const BadgeCreatedModal: FC = (props: BadgeCreatedModalP