diff --git a/.circleci/config.yml b/.circleci/config.yml index 4c15af0f8..933f28cfa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,23 +2,21 @@ version: 2.1 defaults: &defaults docker: - - image: node:16.14 + - image: node:22.13 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 + - image: cimg/python:3.12.1-browsers 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 - pip3 install awscli --upgrade + apt install python3-pip python-is-python3 python3.11-venv -y + curl "https://s3.amazonaws.com/aws-cli/awscli-bundle.zip" -o "awscli-bundle.zip" + unzip awscli-bundle.zip + ./awscli-bundle/install -i /usr/local/aws -b /usr/local/bin/aws install_dependency: &install_dependency name: Installation of build and deployment dependencies. @@ -60,14 +58,6 @@ running_yarn_sb_build: &running_yarn_sb_build source buildenvvar yarn sb:build -running_yarn_test: &running_yarn_test - name: Running Yarn Test Build - command: | - yarn install - yarn cypress install - yarn build - yarn cy:ci - workspace_persist: &workspace_persist root: . paths: @@ -98,26 +88,6 @@ build_steps: &build_steps # Initialization. - run: *running_yarn_sb_build - persist_to_workspace: *workspace_persist -test_steps: &test_steps # Initialization. - - checkout - - setup_remote_docker - - restore_cache: - key: test-node-modules-{{ checksum "yarn.lock" }} - - 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: - path: cypress/test-report - - store_artifacts: - path: cypress/videos - - store_artifacts: - path: cypress/screenshots - deploy_steps: &deploy_steps - checkout - attach_workspace: *workspace_attach @@ -173,14 +143,6 @@ jobs: 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: <<: *deploy_defaults @@ -277,7 +239,4 @@ workflows: filters: &filters-prod branches: only: - - master - - - test-dev: - context: org-global + - master \ No newline at end of file diff --git a/.environments/.env.dev b/.environments/.env.dev index d3f5fecf3..ab1b994f6 100644 --- a/.environments/.env.dev +++ b/.environments/.env.dev @@ -6,9 +6,6 @@ REACT_APP_ENABLE_TCA_CERT_MONETIZATION=false REACT_APP_STRIPE_API_KEY=pk_test_rfcS49MHRVUKomQ9JgSH7Xqz REACT_APP_STRIPE_API_VERSION=2020-08-27 -# Vanilla Forums -REACT_APP_VANILLA_ACCESS_TOKEN=va.JApNvUOx3549h20I6tnl1kOQDc75NDIp.0jG3dA.EE3gZgV - # DataDogLogging REACT_APP_DATADOG_PUBLIC_TOKEN=puba0825671e469d16f940c5a30dc738f11 diff --git a/.environments/.env.prod b/.environments/.env.prod index bac66716c..b4c21353f 100644 --- a/.environments/.env.prod +++ b/.environments/.env.prod @@ -6,9 +6,6 @@ REACT_APP_ENABLE_TCA_CERT_MONETIZATION=false REACT_APP_STRIPE_API_KEY=pk_live_m3bCBVSfkfMOEp3unZFRsHXi REACT_APP_STRIPE_API_VERSION=2020-08-27 -# Vanilla Forums -REACT_APP_VANILLA_ACCESS_TOKEN=va.JApNvUOx3549h20I6tnl1kOQDc75NDIp.0jG3dA.EE3gZgV - # DataDogLogging REACT_APP_DATADOG_PUBLIC_TOKEN=puba0825671e469d16f940c5a30dc738f11 diff --git a/.environments/.env.qa b/.environments/.env.qa index 1c0688542..96332984b 100644 --- a/.environments/.env.qa +++ b/.environments/.env.qa @@ -6,9 +6,6 @@ REACT_APP_ENABLE_TCA_CERT_MONETIZATION=false REACT_APP_STRIPE_API_KEY=pk_test_rfcS49MHRVUKomQ9JgSH7Xqz REACT_APP_STRIPE_API_VERSION=2020-08-27 -# Vanilla Forums -REACT_APP_VANILLA_ACCESS_TOKEN=va.JApNvUOx3549h20I6tnl1kOQDc75NDIp.0jG3dA.EE3gZgV - # DataDogLogging REACT_APP_DATADOG_PUBLIC_TOKEN=puba0825671e469d16f940c5a30dc738f11 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 2b7a30cb0..1810a64c1 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,7 +1,12 @@ -Related JIRA Ticket: -https://topcoder.atlassian.net/browse/ + + +## Related JIRA Ticket: +https://topcoder.atlassian.net/browse/ -# What's in this PR? - - \ No newline at end of file + + +# What's in this PR? diff --git a/.nvmrc b/.nvmrc index c818c7b00..6fa8dec4c 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16.15.0 \ No newline at end of file +22.13.0 diff --git a/README.md b/README.md index a49e03dac..be4a28c20 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ e.g.: `PROD-001 #comment adding readme notes #time 45m` - Typescript - React Scripts -This app uses React 18, Typescript 4, and Node 16. +This app uses React 18, Typescript 4, and Node 22. ### IDE @@ -103,13 +103,13 @@ Once nvm is installed, run: At the root of the project directory you'll notice a file called `.nvmrc` which specifies the node version used by the project. The command `nvm use` will use the version specified in the file if no version is supplied on the command line. See [the nvm Github README](https://github.com/nvm-sh/nvm/blob/master/README.md#nvmrc) for more information on setting this up. ->**NOTE:** The current node version mentioned in the `.nvmrc` is `16.15.0` +>**NOTE:** The current node version mentioned in the `.nvmrc` is `22.13.0` You can verify the versions of `nvm`, `node`, and `npm` using the commands below. | Command | Supported Version | | ----------------- | -------- | | `% npm -v` | 8.5.5 | -| `% node -v` | v16.15.0 | +| `% node -v` | v22.13.0 | | `% nvm --version` | 0.39.1 | | `% nvm current` | v15.15.0 | diff --git a/cypress.config.ts b/cypress.config.ts deleted file mode 100644 index b6188278f..000000000 --- a/cypress.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -import task from '@cypress/code-coverage/task' -import { defineConfig } from 'cypress' - -export default defineConfig({ - defaultCommandTimeout: 10000, - e2e: { - // 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, - viewportWidth: 1280, - }, - fixturesFolder: false, - reporter: 'junit', - reporterOptions: { - mochaFile: 'cypress/test-report/test-result-[hash].xml', - toConsole: false, - }, - screenshotOnRunFailure: true, - video: true, -}) - -// adds the config to node setup events -function setUpNodeEvents( - on: Cypress.PluginEvents, - config: Cypress.PluginConfigOptions -): Cypress.PluginConfigOptions { - task(on, config) - return config -} diff --git a/cypress/.eslintrc b/cypress/.eslintrc deleted file mode 100644 index 0f1b15145..000000000 --- a/cypress/.eslintrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": ["plugin:cypress/recommended"] -} diff --git a/cypress/e2e/home/home.spec.ts b/cypress/e2e/home/home.spec.ts deleted file mode 100644 index 43c584180..000000000 --- a/cypress/e2e/home/home.spec.ts +++ /dev/null @@ -1,12 +0,0 @@ -describe('Landing Page', () => { - - beforeEach(() => cy.visit('/')) - - it('loads landing page should be successfully', () => { - cy.get('[data-id="root"]').should('be.visible') - }) - - it.skip('loads landing page should fail', () => { - cy.get('[data-id="root"]').should('not.be.visible') - }) -}) diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts deleted file mode 100644 index 7d3a21716..000000000 --- a/cypress/support/commands.ts +++ /dev/null @@ -1,8 +0,0 @@ -/// - -Cypress.on('uncaught:exception', () => { - // returning false here prevents Cypress from failing the test - return false -}) - -export {} \ No newline at end of file diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts deleted file mode 100644 index cdb3e6d83..000000000 --- a/cypress/support/e2e.ts +++ /dev/null @@ -1,2 +0,0 @@ -import '@cypress/code-coverage/support' -import './commands' \ No newline at end of file diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json deleted file mode 100644 index 2aa16b188..000000000 --- a/cypress/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../tsconfig.json", - "include": ["./**/*.ts"], - "exclude": [], - "compilerOptions": { - "types": ["cypress"], - "lib": ["es2015", "dom"], - "isolatedModules": false, - "allowJs": true, - "noEmit": true - } -} \ No newline at end of file diff --git a/package.json b/package.json index 05c805a94..b8bea93e0 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,6 @@ "lint:fix": "yarn lint --fix", "test": "craco test --watchAll", "test:no-watch": "craco test --watchAll=false --passWithNoTests", - "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", "sb": "storybook dev -p 6006", @@ -24,45 +22,35 @@ "@datadog/browser-logs": "^4.21.2", "@heroicons/react": "^1.0.6", "@popperjs/core": "^2.11.8", - "@segment/analytics-next": "^1.53.3", "@sprig-technologies/sprig-browser": "^2.20.1", - "@storybook/addon-actions": "^7.0.5", - "@storybook/react": "^7.0.5", + "@storybook/addon-actions": "7.6.10", + "@storybook/react": "7.6.10", "@stripe/react-stripe-js": "1.13.0", "@stripe/stripe-js": "1.41.0", - "@tanstack/react-table": "^8.11.7", - "@types/testing-library__jest-dom": "^5.14.5", "apexcharts": "^3.36.0", - "axios": "^1.1.2", + "axios": "^1.7.9", "browser-cookies": "^1.2.0", "city-timezones": "^1.2.1", "classnames": "^2.3.2", - "contentful": "^9.2.5", + "contentful": "^9.3.7", "country-calling-code": "0.0.3", - "crypto-js": "^4.1.1", + "crypto-js": "^4.2.0", "customize-cra": "^1.0.0", "date-fns": "^2.30.0", - "dompurify": "^2.4.0", + "dompurify": "^2.5.4", "draft-js": "^0.10.4", "draft-js-export-html": "^1.2.0", "draft-js-markdown-shortcuts-plugin": "^0.3.0", "draft-js-plugins-editor": "^2.0.3", - "express": "^4.18.2", + "express": "^4.21.2", "express-fileupload": "^1.4.0", "express-interceptor": "^1.2.0", - "fflate": "^0.7.4", - "filestack-react": "^2.0.0", - "focus-trap-react": "^6.0.0", - "get-parameter-names": "^0.3.0", "highcharts": "^10.3.3", "highcharts-react-official": "^3.2.0", "highlight.js": "^11.6.0", "html2canvas": "^1.4.1", - "isomorphic-fetch": "^2.2.1", - "joi": "^17.4.0", - "katex": "^0.16.4", "lodash": "^4.17.21", - "markdown-it": "^13.0.1", + "markdown-it": "^13.0.2", "marked": "4.1.1", "moment": "^2.29.4", "moment-duration-format": "^2.3.2", @@ -77,14 +65,12 @@ "react-color": "^2.13.8", "react-contenteditable": "^3.3.6", "react-css-super-themr": "^2.2.0", - "react-date-range": "^1.1.3", "react-datepicker": "^4.14.1", "react-dom": "^18.2.0", "react-dropzone": "^11.3.2", "react-elastic-carousel": "^0.11.5", "react-gtm-module": "^2.0.11", "react-helmet": "^6.1.0", - "react-html-parser": "^2.0.2", "react-markdown": "8.0.6", "react-otp-input": "^3.1.1", "react-popper": "^2.3.0", @@ -96,7 +82,6 @@ "react-scripts": "5.0.1", "react-select": "^5.8.0", "react-spinners": "^0.13.6", - "react-stickynode": "^1.4.1", "react-toastify": "^9.0.8", "react-tooltip": "5.11.1", "redux": "^4.2.0", @@ -105,21 +90,15 @@ "redux-promise": "^0.6.0", "redux-promise-middleware": "^6.1.3", "redux-thunk": "^2.4.1", - "rehype-katex": "^6.0.2", - "rehype-raw": "^6.1.1", - "rehype-stringify": "^9.0.3", "remark-breaks": "^3.0.2", "remark-frontmatter": "^4.0.1", "remark-gfm": "^3.0.1", - "remark-math": "^5.1.1", "remove": "^0.1.5", - "sanitize-html": "^2.7.2", - "sass": "^1.55.0", - "shortid": "^2.2.16", + "sanitize-html": "^2.12.1", + "sass": "^1.79.0", "styled-components": "^5.3.6", "swr": "^1.3.0", "tc-auth-lib": "topcoder-platform/tc-auth-lib#1.0.27", - "turndown": "^4.0.2", "typescript": "^4.8.4", "universal-navigation": "https://github.com/topcoder-platform/universal-navigation#9fc50d938be7182", "uuid": "^9.0.0" @@ -133,20 +112,18 @@ "@babel/preset-typescript": "^7.18.6", "@babel/runtime": "^7.19.4", "@craco/craco": "^7.1.0", - "@cypress/code-coverage": "^3.10.0", "@jackwilsdon/craco-use-babelrc": "1.0.0", - "@storybook/addon-essentials": "^7.0.5", - "@storybook/addon-interactions": "^7.0.5", - "@storybook/addon-links": "^7.0.5", - "@storybook/blocks": "^7.0.5", - "@storybook/preset-create-react-app": "^7.0.5", - "@storybook/react-webpack5": "^7.0.5", + "@storybook/addon-essentials": "7.6.10", + "@storybook/addon-interactions": "7.6.10", + "@storybook/addon-links": "7.6.10", + "@storybook/blocks": "7.6.10", + "@storybook/preset-create-react-app": "7.6.10", + "@storybook/react-webpack5": "7.6.10", "@storybook/testing-library": "^0.0.14-next.2", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^14.4.3", "@types/axios": "^0.14.0", - "@types/cypress": "^1.1.3", "@types/dompurify": "^2.3.4", "@types/highlightjs": "^9.12.2", "@types/jest": "^29.1.2", @@ -155,7 +132,7 @@ "@types/marked": "4.0.7", "@types/node": "^18.8.5", "@types/reach__router": "^1.3.11", - "@types/react": "^18.0.21", + "@types/react": "18.0.35", "@types/react-datepicker": "^4.11.2", "@types/react-dom": "^18.0.6", "@types/react-gtm-module": "^2.0.1", @@ -167,6 +144,7 @@ "@types/redux-promise": "^0.5.29", "@types/sanitize-html": "^2.6.2", "@types/systemjs": "^6.1.1", + "@types/testing-library__jest-dom": "^5.14.5", "@types/uuid": "^8.3.4", "@typescript-eslint/eslint-plugin": "^5.30.6", "@typescript-eslint/parser": "^5.30.6", @@ -184,13 +162,11 @@ "craco-resolve-url-loader": "^1.0.0", "cross-env": "^7.0.3", "css-loader": "3.5.3", - "cypress": "^10.10.0", "eslint": "^8.25.0", "eslint-config-airbnb": "^19.0.4", "eslint-config-react-app": "^7.0.1", "eslint-config-react-important-stuff": "^3.0.0", "eslint-import-resolver-typescript": "^3.2.5", - "eslint-plugin-cypress": "^2.12.1", "eslint-plugin-import": "^2.25.3", "eslint-plugin-ordered-imports": "^0.6.0", "eslint-plugin-react": "^7.28.0", @@ -210,10 +186,10 @@ "react-docgen-typescript": "^2.2.2", "react-hot-loader": "^4.3.3", "resolve-url-loader": "^5.0.0", - "sass-loader": "^13.1.0", + "sass-loader": "^13.3.3", "serve": "^14.0.1", "start-server-and-test": "^1.14.0", - "storybook": "^7.0.5", + "storybook": "7.6.10", "style-loader": "^3.3.1", "systemjs-webpack-interop": "^2.3.7", "tsconfig-paths-webpack-plugin": "^4.0.1", @@ -224,6 +200,10 @@ "webpack-dev-server": "^4.11.1", "webpack-merge": "^5.8.0" }, + "resolutions": { + "@types/react": "18.0.35", + "string-width": "4.2.0" + }, "browserslist": { "production": [ ">0.2%", @@ -250,7 +230,7 @@ ] }, "volta": { - "node": "16.15.0", - "yarn": "1.22.19" + "node": "22.13.0", + "yarn": "1.22.22" } } diff --git a/src/apps/admin/src/index.ts b/src/apps/admin/src/index.ts deleted file mode 100644 index db1cc3082..000000000 --- a/src/apps/admin/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { - adminRoutes, - rootRoute as adminRootRoute, -} from './admin.routes' diff --git a/src/apps/dev-center/README.md b/src/apps/dev-center/README.md index adde14a01..ee5fd3480 100644 --- a/src/apps/dev-center/README.md +++ b/src/apps/dev-center/README.md @@ -1,16 +1,3 @@ -# Dev Center Configuration - -The app requires two environment variables, which contain the space id and the key used to access contentful and retrieve Thrive Articles. - -You should create a file named `.env` in the root folder, and write inside the following lines: - -```sh -REACT_APP_CONTENTFUL_EDU_SPACE_ID= -REACT_APP_CONTENTFUL_EDU_CDN_API_KEY= -``` - -We should use the same space ID and API Key as Topcoder Thrive, these are for fetching Thrive articles and videos in the landing page. - ## Landing page We can configure up to 5 articles shown on the landing page. The articles can be from Topcoder Thrive and/or Topcoder Blog. diff --git a/src/apps/dev-center/src/assets/i/index.tsx b/src/apps/dev-center/src/assets/i/index.tsx index de877c783..ef79f130f 100644 --- a/src/apps/dev-center/src/assets/i/index.tsx +++ b/src/apps/dev-center/src/assets/i/index.tsx @@ -2,8 +2,10 @@ import { ReactComponent as ApiCornerIcon } from './api-corner-icon.svg' import { ReactComponent as ApiIcon } from './api-icon.svg' import { ReactComponent as CommunityAppCornerIcon } from './community-app-corner-icon.svg' import { ReactComponent as CommunityAppIcon } from './community-app-icon.svg' +import { ReactComponent as WorkManagerIcon } from './work-manager-icon.svg' export { ApiCornerIcon } export { ApiIcon } export { CommunityAppCornerIcon } export { CommunityAppIcon } +export { WorkManagerIcon } diff --git a/src/apps/dev-center/src/assets/i/work-manager-icon.svg b/src/apps/dev-center/src/assets/i/work-manager-icon.svg new file mode 100644 index 000000000..de063a36b --- /dev/null +++ b/src/apps/dev-center/src/assets/i/work-manager-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/DevCenterLandingPage.tsx b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/DevCenterLandingPage.tsx index 26b0de173..899452ef3 100644 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/DevCenterLandingPage.tsx +++ b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/DevCenterLandingPage.tsx @@ -1,6 +1,5 @@ import { FC } from 'react' -import { DevCenterArticlesection } from './dev-center-articles-section' import { DevCenterGetStarted } from './dev-center-get-started' import { DevCenterHeader } from './dev-center-header' import styles from './DevCenterLandingPage.module.scss' @@ -9,7 +8,6 @@ const DevCenter: FC = () => (
-
) diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/ArticleCard/ArticleCard.module.scss b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/ArticleCard/ArticleCard.module.scss deleted file mode 100644 index 700e92492..000000000 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/ArticleCard/ArticleCard.module.scss +++ /dev/null @@ -1,91 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.outerContainer{ - border-radius: 8px; - position: relative; - background-size: 100% 100%; - overflow: hidden; - - @include ltemd{ - background-size: cover; - } - - .innerContainer { - width: 100%; - height: 100%; - background: linear-gradient(180deg, rgba(0, 0, 0, 0) 30.21%, #000000 100%); - - .playButton { - width: 38px; - height: 38px; - color: $tc-white; - position: absolute; - left: calc(50% - 19px); - top: calc(50% - 19px); - } - - .container { - padding-left: $sp-6; - padding-right: $sp-6; - padding-bottom: $sp-6; - position: absolute; - bottom: 0px; - width: 100%; - - .topLine { - display: flex; - flex-direction: row; - justify-content: space-between; - margin-bottom: 14px; - align-items: center; - } - - .author { - opacity: 0.72; - text-transform: capitalize; - display: block; - margin-bottom: $sp-4; - } - - .summary { - @include font-weight-normal; - display: block; - white-space: nowrap; - overflow: hidden; - margin-bottom: $sp-4; - text-overflow: ellipsis; - } - - .readMore { - font-weight: 700; - font-size: 16px; - line-height: 16px; - text-transform: uppercase; - } - - - } - - - } -} - -.mainArticle { - - @include ltemd{ - aspect-ratio: 4 / 3; - } -} - -.smallArticle { - aspect-ratio: 4 /3; - - @include ltemd{ - width: 100%; - max-height: 240px; - } -} - -.mainArticle:hover, .smallArticle:hover{ - cursor: pointer; -} diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/ArticleCard/ArticleCard.tsx b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/ArticleCard/ArticleCard.tsx deleted file mode 100644 index ba38a80ba..000000000 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/ArticleCard/ArticleCard.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import { FC } from 'react' -import classNames from 'classnames' - -import { THRIVE_PAGE } from '~/apps/dev-center/src/config' -import { IconSolid } from '~/libs/ui' - -import { DevCenterTag } from '../../dev-center-tag' -import { isThriveArticle } from '../Articles' -import { BlogPost, ThriveArticle } from '../models' - -import styles from './ArticleCard.module.scss' - -interface ArticleDetails { - author: string - image: string - isThrive: boolean - isVideo: boolean - summary: string - tagText: string - url: string -} - -function openArticle(url: string): void { - window.open( - url, - '_blank', // This is what makes it open in a new window. - ) -} - -function getTagText(isThrive: boolean, isVideo: boolean): string { - return isThrive - ? isVideo - ? 'Thrive Video' - : 'Thrive Article' - : 'Success Story' -} - -function getArticleContent( - article: ThriveArticle | BlogPost, - isThrive: boolean, -): string { - return isThrive ? (article as ThriveArticle).content : (article as BlogPost).contentSnippet -} - -function getArticleDetails(article: ThriveArticle | BlogPost): ArticleDetails { - const isThrive: boolean = isThriveArticle(article) - const thriveArticle: ThriveArticle = article as ThriveArticle - const blogPost: BlogPost = article as BlogPost - const isVideo: boolean = isThrive && thriveArticle.type === 'Video' - - const tagText: string = getTagText(isThrive, isVideo) - - const content: string = getArticleContent(article, isThrive) - const regex: RegExp = /(<([^>]+)>)/gi - const summary: string = content.replace(regex, '') // Remove html from the content string - const url: string = isThrive - ? `${THRIVE_PAGE}/articles/${thriveArticle.slug}` - : blogPost.link - const author: string = !isThrive ? blogPost.creator : '' - const image: string = isThrive - ? thriveArticle.featuredImage.fields.file.url - : blogPost.featuredImage - - return { - author, - image, - isThrive, - isVideo, - summary, - tagText, - url, - } -} - -function getOuterClass(isMain: boolean, className: string): string { - return classNames( - className, - styles.outerContainer, - isMain ? styles.mainArticle : styles.smallArticle, - ) -} - -interface ArticleCardProps { - article: ThriveArticle | BlogPost - className?: string - isMain: boolean -} - -const ArticleCard: FC = props => { - const outerClass: string = getOuterClass(props.isMain, props.className ?? '') - const { - isThrive, - isVideo, - tagText, - summary, - url, - author, - image, - }: ArticleDetails = getArticleDetails(props.article) - - return ( -
-
- {isThrive && isVideo && ( - - )} -
-
- - {isThrive && ( - - {(props.article as ThriveArticle).readTime} - - )} -
- {props.isMain ? ( -

- {props.article.title} -

- ) : ( -

- {props.article.title} -

- )} - {!isThrive && ( - - {author} - - )} - {props.isMain && ( - <> - - {summary} - - - READ MORE - - - )} -
-
-
- ) -} - -export default ArticleCard diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/ArticleCard/index.tsx b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/ArticleCard/index.tsx deleted file mode 100644 index faf7c7bbc..000000000 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/ArticleCard/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default as ArticleCard } from './ArticleCard' diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/Articles.ts b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/Articles.ts deleted file mode 100644 index af43cfba4..000000000 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/Articles.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { API_BASE, WP_CONTENT } from '~/apps/dev-center/src/config' - -import { BlogPost, ThriveArticle } from './models' - -/** Check if the article is a BlogPost or a ThriveArticle */ -export function isThriveArticle(article: BlogPost | ThriveArticle): article is ThriveArticle { - return (article as ThriveArticle).readTime !== undefined -} - -/** This is the default image to be used for blog posts that do not provide an url to the hero image */ -const DEFAULT_BLOG_IMAGE: string = `${WP_CONTENT}/uploads/2017/04/SRM_Blog.png` - -/** Get the blog with the given url, or return undefined if the blog couldn't be fetched */ -export async function getBlog(url: string): Promise { - try { - const response: Response = await fetch(`${API_BASE}/blog?limit=200`) - const data: Array = await response.json() - const blog: BlogPost = data.filter(x => x.link === url)[0] - // If the returned data do not contain the URL to the image, use the default one - if (!blog.featuredImage) { - blog.featuredImage = DEFAULT_BLOG_IMAGE - } - - return blog - } catch (e) { - return undefined - } -} diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/CardSection/CardSection.module.scss b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/CardSection/CardSection.module.scss deleted file mode 100644 index d869817f4..000000000 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/CardSection/CardSection.module.scss +++ /dev/null @@ -1,102 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - width: 100%; - justify-content: center; - - .innerContainer { - display: grid; - grid-template-columns: 2fr 1fr 1fr; - grid-template-rows: 1fr 1fr; - gap: 32px; - - @include xl{ - grid-template-columns: 1fr 1fr; - grid-template-rows: 2fr 1fr 1fr; - max-width: 812px; - margin: auto; - } - - @include lg{ - grid-template-columns: 1fr 1fr; - grid-template-rows: 2fr 1fr 1fr; - } - - @include ltemd{ - display: flex; - flex-direction: column; - max-width: 400px; - margin: auto; - } - - .mainItem { - grid-row: 1 / span 2; - grid-column: 1; - - @include xl{ - grid-row: 1 ; - grid-column: 1 / span 2; - } - - @include lg{ - grid-row: 1 ; - grid-column: 1 / span 2; - } - } - .item2 { - grid-row: 1; - grid-column: 2; - - @include xl{ - grid-row: 2; - grid-column: 1; - } - @include lg{ - grid-row: 2; - grid-column: 1; - } - } - .item3 { - grid-row: 1; - grid-column: 3; - - @include xl{ - grid-row: 2; - grid-column: 2; - } - @include lg{ - grid-row: 2; - grid-column: 2; - } - } - - .item4 { - grid-row: 2; - grid-column: 2; - - @include xl{ - grid-row: 3; - grid-column: 1; - } - @include lg{ - grid-row: 3; - grid-column: 1; - } - } - .item5 { - grid-row: 2; - grid-column: 3; - - @include xl{ - grid-row: 3; - grid-column: 2; - } - @include lg{ - grid-row: 3; - grid-column: 2; - } - } - } -} - - diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/CardSection/CardSection.tsx b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/CardSection/CardSection.tsx deleted file mode 100644 index 3617b1f0f..000000000 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/CardSection/CardSection.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { ContentfulClientApi, createClient } from 'contentful' -import { FC, useEffect, useState } from 'react' - -import { ArticleCard } from '../ArticleCard' -import { getBlog } from '../Articles' -import { ArticlesUrl, defaultBlogs } from '../articles.config' -import { ArticleType, BlogPost, ThriveArticle } from '../models' - -import styles from './CardSection.module.scss' - -const CardSection: FC = () => { - const [articles, setArticles]: [ - Array, - React.Dispatch>> - ] = useState>([]) - - useEffect(() => { - const client: ContentfulClientApi = createClient({ - accessToken: ( - process.env.REACT_APP_CONTENTFUL_EDU_CDN_API_KEY - ?? 'EXl1Y8-6VFyHBqJqlxGp2LKmyNJJPutDbKH977G07eg' - ), - space: ( - process.env.REACT_APP_CONTENTFUL_EDU_SPACE_ID - ?? 'piwi0eufbb2g' - ), - }) - Promise.all( - ArticlesUrl.map(async (articleUrl, idx): Promise => { - switch (articleUrl.type) { - case ArticleType.Thrive: - return (await client.getEntry(articleUrl.url)).fields as ThriveArticle - - case ArticleType.Blog: { - const blog: BlogPost - = (await getBlog(articleUrl.url)) - ?? defaultBlogs[idx] - return blog - } - - default: { - return undefined - } - } - }), - ) - .then(arr => setArticles(arr.filter(Boolean) as Array)) - }, []) - - const articleStyles: Array = [ - styles.mainItem, - styles.item2, - styles.item3, - styles.item4, - styles.item5, - ] - - return ( -
-
- {articles.map((article, index) => ( - - ))} -
-
- ) -} - -export default CardSection diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/CardSection/index.tsx b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/CardSection/index.tsx deleted file mode 100644 index 5f4ddacdc..000000000 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/CardSection/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default as CardSection } from './CardSection' diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/DevCenterArticlesSection.module.scss b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/DevCenterArticlesSection.module.scss deleted file mode 100644 index 8405de69b..000000000 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/DevCenterArticlesSection.module.scss +++ /dev/null @@ -1,26 +0,0 @@ -@import "@libs/ui/styles/includes"; - -.container { - background-color: $black-5; - width: 100%; - height: fit-content; - padding: 80px 0px; - margin-top: 64px; - - @include ltemd { - padding: 40px 0px; - } - - .title { - @include font-black-100; - font-weight: 500; - font-size: 44px; - line-height: 44px; - margin-bottom: $sp-8; - - @include ltemd { - font-size: 27px; - line-height: 28px; - } - } -} diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/DevCenterArticlesSection.tsx b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/DevCenterArticlesSection.tsx deleted file mode 100644 index 2987ef6d9..000000000 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/DevCenterArticlesSection.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { FC } from 'react' - -import { ContentLayout } from '~/libs/ui' - -import { CardSection } from './CardSection' -import styles from './DevCenterArticlesSection.module.scss' - -const DevCenterArticlesection: FC = () => ( -
- -

Success Stories And Articles

- -
-
-) - -export default DevCenterArticlesection diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/articles.config.ts b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/articles.config.ts deleted file mode 100644 index bc56e7c9f..000000000 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/articles.config.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* eslint-disable max-len */ -import { BLOG_PAGE, WP_CONTENT } from '~/apps/dev-center/src/config' - -import { ArticleEntry, ArticleType, BlogPost } from './models' - -/** - * This array defiens which thirive articles and blog pasts should be shown. - * The first element in the array will be the main article. - * For Thrive Articles, the url should be the Contentful ID. - * For Blog Posts, the url is the url linking to the post on topcoder.com - * Example - * { - * type: ArticleType.Blog, - * url: `${BLOG_PAGE}/talent-as-a-service-taas-a-brilliant-solution-to-the-talent-gap/`, - * } - * { - * type: ArticleType.Thrive, - * url: '6KP9iCELCsLWTSWirplWs2', - * } - */ -export const ArticlesUrl: Array = [ - { - type: ArticleType.Thrive, - url: '6KP9iCELCsLWTSWirplWs2', - }, - { - type: ArticleType.Thrive, - url: '1afUuuZVt1JI14iRkAIqba', - }, - { - type: ArticleType.Thrive, - url: 'puRqAymWv7hjjyHyunGK2', - }, - { - type: ArticleType.Thrive, - url: '6yZJ8xSYTEqVJhMeQyA7BA', - }, - { - type: ArticleType.Thrive, - url: '3QOvaHaSxjF26owDurtfKZ', - }, -] - -/** - * This array contains the default blog posts to be shown if the ones specified above are not available. - */ -export const defaultBlogs: Array = [ - { - contentSnippet: 'In light of the incredible speed of innovation, specialized tech talent has never been more critical to business success. Yet access to that talent remains frustratingly difficult for many companies. According to the Society for Human Resources Management, 83% of businesses are having trouble recruiting suitable candidates for their open positions, particularly when it comes […]\nThe post Talent as a Service (TaaS): A Brilliant Solution to the Talent Gap appeared first on Topcoder.', - creator: 'Kiran Hampapura', - featuredImage: `${WP_CONTENT}/uploads/2019/11/taashero.jpg`, - link: `${BLOG_PAGE}/talent-as-a-service-taas-a-brilliant-solution-to-the-talent-gap/`, - title: 'Talent as a Service (TaaS): A Brilliant Solution to the Talent Gap', - }, - { - contentSnippet: 'Can our Topcoder accounts be hacked? Can our well-earned cash be stolen away through the platform? Can customers suffer from intellectual theft? These sensitive questions belong to a discussion on a beyond-interesting topic: security. Honoring Topcoder’s security themed month, we want to raise awareness on what cyber security means for members and customers. We turned […]\nThe post Securing A Safe Work System For Members And Customers With John Wheeler - The Topcoder Nation Show #18 appeared first on Topcoder.', - creator: 'mahestro', - featuredImage: `${WP_CONTENT}/uploads/2022/07/00-tcn-show-18-john-wheeler.png`, - link: `${BLOG_PAGE}/securing-a-safe-work-system-for-members-and-customers-with-john-wheeler-the-topcoder-nation-show-18/`, - title: 'Securing A Safe Work System For Members And Customers With John Wheeler – The Topcoder Nation Show #18', - }, - { - contentSnippet: 'Job opportunities, upskilling, and mentoring are traits that identify the endeavor that this young gentleman is leading in Africa. Meet Abiodun (), born and raised in Lagos; he aims to close the gap between the tech talent in his region and opportunities, leveraging Topcoder as a medium to make it happen. Abiodun loves live music […]\nThe post Building A Tech Community In Africa With Code_Abbey – The Topcoder Nation Show #17 appeared first on Topcoder.', - creator: 'mahestro', - featuredImage: `${WP_CONTENT}/uploads/2022/06/00-tcn-show-17-code_abby-and-gigs-in-africa.png`, - link: `${BLOG_PAGE}/building-a-tech-community-in-africa-with-code_abbey-the-topcoder-nation-show-17/`, - title: 'Building A Tech Community In Africa With Code_Abbey – The Topcoder Nation Show #17', - }, - { - contentSnippet: 'CellPhoneService We just need to do the calculations described in the statement. One part of the calculations that may be tricky for beginners is the fee per each started minute of a call. If we have a call that takes S seconds, the number of minutes we’ll paying for can be computed by dividing S […]\nThe post Single Round Match 833 Editorials appeared first on Topcoder.', - creator: 'misof', - featuredImage: `${WP_CONTENT}/uploads/2017/04/SRM_Blog.png`, - link: `${BLOG_PAGE}/single-round-match-833-editorials/`, - title: 'Single Round Match 833 Editorials', - }, - { - contentSnippet: 'TwoDimensionalSort Imagine that we label rows of the board A to Z from top to bottom. If we got each rook X into its row X, the board would surely be sorted. With N rooks we can always achieve that in at most 2*N moves. In the first N moves we’ll move some rooks horizontally […]\nThe post TCO22 Round 3 Editorial appeared first on Topcoder.', - creator: 'misof', - featuredImage: `${WP_CONTENT}/uploads/2017/04/SRM_Blog.png`, - link: `${BLOG_PAGE}/tco22-round-3-editorial/`, - title: 'TCO22 Round 3 Editorial', - }, -] diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/index.tsx b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/index.tsx deleted file mode 100644 index 664c74822..000000000 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/index.tsx +++ /dev/null @@ -1 +0,0 @@ -export { default as DevCenterArticlesection } from './DevCenterArticlesSection' diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/models/articleentry.model.ts b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/models/articleentry.model.ts deleted file mode 100644 index bf5466632..000000000 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/models/articleentry.model.ts +++ /dev/null @@ -1,10 +0,0 @@ -export enum ArticleType { - Blog = 'blog', - Thrive = 'thrive', -} - -/** The type of the objects used to specy which articles should be shown inside articles.config.ts */ -export interface ArticleEntry { - type: ArticleType, - url: string, -} diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/models/blogpost.model.ts b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/models/blogpost.model.ts deleted file mode 100644 index 69d249f4a..000000000 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/models/blogpost.model.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** The type of the blog post retrieved from the api */ -export interface BlogPost { - contentSnippet: string - creator: string - featuredImage: string - link: string - title: string -} diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/models/index.ts b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/models/index.ts deleted file mode 100644 index e612c6a52..000000000 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/models/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './articleentry.model' -export * from './blogpost.model' -export * from './thrivearticle.model' diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/models/thrivearticle.model.ts b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/models/thrivearticle.model.ts deleted file mode 100644 index 47e84b5d4..000000000 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-articles-section/models/thrivearticle.model.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** The type of the Thrive Articles retrieved from Contentful */ -export interface ThriveArticle { - content: string - contentAuthor: Array<{ fields: { name: string; }; }> - featuredImage: { fields: { file: { url: string; }; title: string; }; } - readTime: string - slug: string - title: string - type?: string -} diff --git a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-get-started/GetStartedCardsContainer/GetStartedCardsContainer.tsx b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-get-started/GetStartedCardsContainer/GetStartedCardsContainer.tsx index a34fbbaae..e052e63ae 100644 --- a/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-get-started/GetStartedCardsContainer/GetStartedCardsContainer.tsx +++ b/src/apps/dev-center/src/dev-center-pages/community-app/landing-page/dev-center-get-started/GetStartedCardsContainer/GetStartedCardsContainer.tsx @@ -1,47 +1,90 @@ import { FC } from 'react' +import { BookOpenIcon } from '@heroicons/react/outline' import { LinkButton } from '~/libs/ui' -import { ApiCornerIcon, ApiIcon, CommunityAppCornerIcon, CommunityAppIcon } from '../../../../../assets/i' +import { + ApiCornerIcon, + ApiIcon, + CommunityAppCornerIcon, + CommunityAppIcon, + WorkManagerIcon, +} from '../../../../../assets/i' import { DevCenterCard } from '../../dev-center-card' import { rootRoute } from '../../../../../dev-center.routes' import styles from './GetStartedCardsContainer.module.scss' const GetStartedCardsContainer: FC = () => ( -
- } - icon={} - title='Community App' - titleClass={styles.communityTitle} - description='Learn about Topcoder Community App and run started code.' - button={( - - )} - /> - } - icon={} - title='Platform UI Storybook' - titleClass={styles.apiTitle} - description='Explore the Platform UI Storybook for UI development.' - button={( - - )} - /> +
+
+ } + icon={} + title='Community App' + titleClass={styles.communityTitle} + description='Learn about Topcoder Community App and run started code.' + button={( + + )} + /> + } + icon={} + title='Platform UI' + titleClass={styles.apiTitle} + description='Check out instructions on how to get started with the Platform UI.' + button={( + + )} + /> +
+
+ } + icon={} + title='Platform UI Storybook' + titleClass={styles.apiTitle} + description='Explore the Platform UI Storybook for UI development.' + button={( + + )} + /> + } + icon={} + title='Work Manager' + titleClass={styles.communityTitle} + description='Get familiar with the Work Manager and execute the starter code.' + button={( + + )} + /> +
) diff --git a/src/apps/dev-center/src/dev-center-pages/platform-ui-app/getting-started/GettingStartedGuide.md b/src/apps/dev-center/src/dev-center-pages/platform-ui-app/getting-started/GettingStartedGuide.md new file mode 100644 index 000000000..b9017d491 --- /dev/null +++ b/src/apps/dev-center/src/dev-center-pages/platform-ui-app/getting-started/GettingStartedGuide.md @@ -0,0 +1,186 @@ +# Platform UI + +The Platform UI is the official Topcoder web app to host all modern user interfaces to be used by all users. + +All future user interfaces at Topcoder will be implemented here. Pre-existing user interfaces will be ported to here over time until this is the only user interface any user sees when interacting with Topcoder. + +## Local Environment Setup + +### Install VS Code + +Preferably, use the VS Code IDE for development. + +[https://code.visualstudio.com/download](https://code.visualstudio.com/download) + +### GIT + +Install git on your local machine. This is trivial for working in the community. +You can follow these guides for installing GIT: + +- [Windows](https://local.topcoder-dev.com/devcenter/getting-started#23-install-git) +- [Linux](https://local.topcoder-dev.com/devcenter/getting-started#197-install-git) + +### nvm + +Use the node version manager [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md) to easily and safely manage the required version of NodeJS (aka, node). Download and install it per the instructions for your development operating system. Installing a version of node via `nvm` will also install `npm`. + +> **NOTE:** If the nvm command is not working it might be because the installation failed to update your paths variable properly. To try and fix this try installing nvm again using sudo. + +Once nvm is installed, run: + +```sh +nvm install +``` + +At the root of the project directory you'll notice a file called `.nvmrc` which specifies the node version used by the project. The command `nvm use` will use the version specified in the file if no version is supplied on the command line. +See [the nvm Github README](https://github.com/nvm-sh/nvm/blob/master/README.md#nvmrc) for more information on setting this up. + +> **NOTE:** The current node version mentioned in the `.nvmrc` is `22.13.0` + +You can verify the versions of `nvm`, `node`, and `npm` using the commands below. + +| Command | Supported Version | +| ------------- | ----------------- | +| `% npm -v` | 10.9.2 | +| `% node -v` | v22.13.0 | + +> **NOTE:** The `yarn start` command requires the `NVM_DIR` env variable is set. + +```sh +export NVM_DIR=~/.nvm +``` + +### Hosting + +You will need to add the following line to your hosts file. The hosts file is normally located at `/etc/hosts` (Mac & Linux) or %SYSTEMROOT%\System32\drivers\etc\hosts (Windows). Do not overwrite the existing localhost entry also pointing to 127.0.0.1. + +``` +127.0.0.1 local.topcoder-dev.com +``` + +### Building and serving the application + +1. Open a terminal +2. Run the following commands + +```sh +git clone https://github.com/topcoder-platform/platform-ui.git +cd platform-ui +yarn install +yarn start +``` + +3. Go to https://local.topcoder-dev.com + +> **NOTE**: The site must run on port 443 in order for auth0 to work and for the site to load properly. Mac users will need to run the app with elevated permissions, as in: + +```sh +sudo yarn start +``` + +Run following command to allow node to run apps on ports lowert than 500 (Mac & Linux): + +```sh +sudo setcap 'cap_net_bind_service=+ep' `which node` +``` + +### Local SSL + +SSL is required for authentication to work properly. + +The `yarn start` command serves the site using the cert and key in the /ssl directory, which authorize the `https://local.topcoder-dev.com` URL. + +By overriding the app to use **port 443**, you can use the authorized URL and trust the root CA to avoid SSL errors in the browser. + +> **NOTE:** Mac and Linux users will require running the app with elevated permissions in order to use a port lower than 500. + +Easy way to overcome elevated permissions is to make use of: + +```sh +sudo setcap 'cap_net_bind_service=+ep' `which node` +``` + +For easier development, it is recommended that you add this certificate to your trusted root authorities and as a trused cert in your browser. Google your browser and OS for more info on how to trust cert authorities. + +Otherwise, you will need to override the exception each time you load the site. Firefox users may need to user an incognito browser in order to override the exception. + +## yarn Commands + +| Command | Description | +| ------------------- | -------------------------------------------------------------------------------------- | +| yarn start | Serve dev mode build with the default config | +| yarn build | Build dev mode build with the default config and outputs static files in /build | +| yarn build:prod | Build prod mode build with the prod config and outputs static files in /build | +| yarn demo | Serves the built files (by running yarn:build) for local testing | +| yarn lint | Run eslint against js/x and ts/x files and outputs report | +| yarn lint:fix | Run eslint against js/x and ts/x files, fixes auto-fixable issues, and outputs report | +| yarn sb | Start the storybook dev server | +| yarn sb:build | Build the assets for storybook | + +## App specific setup + +Each Application can have its own setup requirements. Please see each apps's README for further information. + +### Applications hosted under Platform UI + +#### Platform App +This is the "router" app under the whole sum of all Platform UI applications. It will just load all applications and serve one based on the specific route +It also renders the [Universal Navigation](https://github.com/topcoder-platform/universal-navigation)'s header & footer. + +Located `src/apps/platform`. + +#### Account Settings +App that manages user's own settings. + +Located `src/apps/accounts`. + +#### Dev Center +A community-led project to document how to work with Topcoder internal applications. + +Located `src/apps/dev-center`. + +#### Gamification Admin +Application that allows administrators to CRUD badges and de/assign them to specific users. + +Located `src/apps/gamification-admin`. + +#### Learn +Application that serves 3rd-party educational content. + +Located `src/apps/learn`. + +#### Onboarding App +Application that helps new users with the onboarding on our platform. + +Located `src/apps/onboarding`. + +#### Profiles App +Application that allows users to manage their own profile data, and allows visitors to view user details and participation statistics for a specific member. + +Located `src/apps/profiles`. + +#### Talent Search App +This is an internal app for finding members based on skills and other search facets. + +Located `src/apps/talent-search`. + +#### Skills Manager +Admin app that allows one to manage the standardized skills. + +Located `src/apps/skills-manager`. + +#### Self Service +Application that allows customers to submit/start challenges self-service. + +Located `src/apps/self-service`. + +#### Wallet App +This app allows members to manage details regarding their payments. + +Located `src/apps/wallet`. + +#### Wallet Admin App +This app allows admins to manage settings regarding payments, payment methods and tax forms. + +Located `src/apps/wallet-admin`. + diff --git a/src/apps/dev-center/src/dev-center-pages/platform-ui-app/getting-started/GettingStartedGuide.module.scss b/src/apps/dev-center/src/dev-center-pages/platform-ui-app/getting-started/GettingStartedGuide.module.scss new file mode 100644 index 000000000..f08c71140 --- /dev/null +++ b/src/apps/dev-center/src/dev-center-pages/platform-ui-app/getting-started/GettingStartedGuide.module.scss @@ -0,0 +1,40 @@ +@import "@libs/ui/styles/includes"; +@import '../../../styles/variables'; + +.contentLayout { + width: 100%; + padding-bottom: 0; + + .contentLayout-outer { + width: 100%; + + .contentLayout-inner { + width: 100%; + overflow: visible; + + padding-top: $sp-4; + + h4 { + margin-top: $sp-4; + } + + table { + th { + font-weight: bold; + } + td { + border: 1px solid; + padding: $sp-1 $sp-4; + } + } + } + } +} + +@include ltelg { + .contentLayout { + .contentLayout-outer { + padding: 0 $sp-4; + } + } +} diff --git a/src/apps/dev-center/src/dev-center-pages/platform-ui-app/getting-started/GettingStartedGuide.tsx b/src/apps/dev-center/src/dev-center-pages/platform-ui-app/getting-started/GettingStartedGuide.tsx new file mode 100644 index 000000000..e56d6b2e5 --- /dev/null +++ b/src/apps/dev-center/src/dev-center-pages/platform-ui-app/getting-started/GettingStartedGuide.tsx @@ -0,0 +1,33 @@ +import * as React from 'react' + +import { Breadcrumb, BreadcrumbItemModel, ContentLayout } from '~/libs/ui' + +import { rootRoute, toolTitle } from '../../../dev-center.routes' +import { LayoutDocHeader, MarkdownDoc } from '../../../dev-center-lib/MarkdownDoc' +import useMarkdown from '../../../dev-center-lib/hooks/useMarkdown' + +import gettingStartedGuide from './GettingStartedGuide.md' +import styles from './GettingStartedGuide.module.scss' + +export const GettingStartedGuide: React.FC = () => { + const { doc, toc, title }: ReturnType = useMarkdown({ uri: gettingStartedGuide }) + const breadcrumb: Array = React.useMemo(() => [ + { name: toolTitle, url: rootRoute || '/' }, + { name: title, url: '#' }, + ], [title]) + + return ( + + + + + + + ) +} + +export default GettingStartedGuide diff --git a/src/apps/dev-center/src/dev-center-pages/work-manager/WorkManager.md b/src/apps/dev-center/src/dev-center-pages/work-manager/WorkManager.md new file mode 100644 index 000000000..7db77e84a --- /dev/null +++ b/src/apps/dev-center/src/dev-center-pages/work-manager/WorkManager.md @@ -0,0 +1,231 @@ +# Work Manager Dev Guide + +The Work Manager is a part of the Topcoder website for creating and managing challenges. This document covers the Windows 10, Linux and MacOS setup of the development environment in detail. + +## Local setup for work manager + +### Install VS Code + +[https://code.visualstudio.com/Download](https://code.visualstudio.com/Download) + +You can use the default options when installing VS Code. + + + +### Install Git + +Before you begin contributing to the project, you'll need to have Git installed on your local machine. It’s essential for working in the community and contributing to the repository. + +Follow the installation guides below for your operating system: + +#### For Windows: + +1. **Download the Git Installer:** + - Visit the [Windows Git Installation Guide](https://git-scm.com/downloads/win). + - Click the link to download the Git installer. + +2. **Run the Installer:** + - Launch the downloaded installer and follow the installation wizard. You can usually accept the default settings, but make sure to select the option to add Git to your system PATH during installation. This makes Git accessible from any command prompt window. + +3. **Verify the Installation:** + - After installation is complete, open a command prompt or Git Bash and type the following command to check if Git was installed correctly: + ``` + git --version + ``` + - If Git is installed properly, you’ll see a version number displayed. + +#### For Linux: + +1. **Install Git via Package Manager:** + - Most Linux distributions come with Git available in their package manager. You can install it by running one of the following commands based on your distribution: + + **For Ubuntu/Debian-based systems:** + ``` + sudo apt update + sudo apt install git + ``` + + **For Fedora:** + ``` + sudo dnf install git + ``` + + **For CentOS/RHEL:** + ``` + sudo yum install git + ``` + +2. **Verify the Installation:** + - Once Git is installed, open a terminal and run the following command to verify the installation: + ``` + git --version + ``` + - You should see the Git version number displayed in the terminal. + +Make sure to check your installation after following these steps to confirm everything is set up correctly. + +### Install NVM + +Use the node version manager [nvm](https://github.com/nvm-sh/nvm/blob/master/README.md) to easily and safely manage the required version of NodeJS (aka, node). Download and install it per the instructions for your development operating system. Installing a version of node via `nvm` will also install `npm`. + +> **NOTE:** If the nvm command is not working it might be because the installation failed to update your paths variable properly. To try and fix this try installing nvm again using sudo. +Once nvm is installed, run: + +```sh +nvm install +``` + +See [the nvm Github README](https://github.com/nvm-sh/nvm/blob/master/README.md#nvmrc) for more information on setting this up. + +To validate the nvm, node or npm versions, you can try this in terminal: + +```terminal +nvm --version +``` + +The output should look like this: + +```terminal +copilot@DESKTOP-CEFAE6N MINGW64 ~ +$ nvm --version +0.39.1 + +copilot@DESKTOP-CEFAE6N MINGW64 ~ +$ +``` + +### Hosts file update + +For windows, open the file `C:\Windows\System32\drivers\etc\hosts` in VS Code. We will add these two lines to the end of the file: + +```sh +127.0.0.1 local.topcoder-dev.com +127.0.0.1 local.topcoder.com +``` + +You will need to save the file using admin privileges. The final file should look like this: + +```sh +# Copyright (c) 1993-2000 Microsoft Corp. +# +# This is a sample HOSTS file used by Microsoft TCP/IP for Windows +# +# This file contains the mappings of iP addresses to host names. +# entry should be kept on an individual line. The IP address should +# be placed in the first column followed by the corresponding hosts +# The IP address and the host name should be separated by at least +# space. +# +# Additionaly, comments (such as these) may be inserted on individual +# lines or following the machine name denoted by a '#' symbol. +# +# For example: +# +# 102.54.94.97 rhino.acme.com # source server +# 38.25.63.10 x.acme.com # x client host + +# localhost name resolution is handled within DNS itself. +# 127.0.0.1 localhost +# ::1 localhost +127.0.0.1 local.topcoder-dev.com +127.0.0.1 local.topcoder.com +``` + +For mac and linux, the hosts file is normally located at `/etc/hosts` and we add the same two lines to that file as well. + +This is how it should like: + +```sh +## +# Host Database +# +# localhost is used to configure the loopback interface +# when the system is booting. Do not change this entry. +## +127.0.0.1 localhost +255.255.255.255 broadcasthost +::1 localhost +127.0.0.1 local.topcoder-dev.com +127.0.0.1 local.topcoder.com +``` + +### Check out the code + +Now that all dependencies are set up, we can check out the code. Note that this command will check out the work-manager source code into a directory named `work-manager`. + +Run this command on the Git Bash command line: + +```terminal +git clone https://github.com/topcoder-platform/work-manager.git +``` + +```terminal +copilot@DESKTOP-CEFAE6N MINGW64 +$ git clone https://github.com/topcoder-platform/work-manager.git +Cloning into 'work-manager'... +remote: Enumerating objects: 9974, done. +remote: Counting objects: 100% (1507/1507), done. +remote: Compressing objects: 100% (507/507), done. +remote: Total 9974 (delta 1162), reused 1027 (delta 1000), pack-reused 8467 (from 3) +Receiving objects: 100% (9974/9974), 4.89 MiB | 2.82 MiB/s, done. +Resolving deltas: 100% (6727/6727), done. + +copilot@DESKTOP-CEFAE6N MINGW64 ~ +$ +``` + +### Build and run the code + +Now that we have the code, we can build it on the VS code terminal. +The first `cd community-app` command just changes us to the directory we created above, after the code was cloned. +At the root of the project directory you'll notice a file called `.nvmrc` which specifies the node version used by the project. The command `nvm use` will use the version specified in the file if no version is supplied on the command line. + +* `cd work-manager` +* `nvm use` will warn you to install v12.17.0 +* `nvm install v12.17.0` + +```terminal +$nvm use +Found '/c/Users/copilot/work-manager/.nvmrc' with version <12.17.0> +Now using node v12.17.0 (npm v6.14.4) +``` + +Once we have the proper Node version installed (12.17.0), we will install the dependencies: + +```terminal +npm i +``` +Once the dependencies have installed we run the app: +```terminal +npm run dev +``` + + +### Install the proxy and run it + +We need to proxy `https` requests through a local proxy since we don't have a valid SSL key. To do this, we use the `local-ssl-proxy` package. You can install this using this command. + +* `npm i -g local-ssl-proxy` You only have to run this once to install the package +* `local-ssl-proxy -n local.topcoder-dev.com -s 443 -t 3000` Every time you want to run the proxy or work on the community app, you will need to run. You will need to grant the proxy admin access. + +**NOTE** - You should run the proxy in a *separate* terminal window, to ensure it's always running. + +```terminal +$ npm i -g local-ss1-proxy +npm WARN deprecated nomnom@1.8.1: Package no longer supported. contact support@npmjs.com for more info. +npm WARN notice [SECURITY] underscore has the following vulnerability: 1 high. Go here for more details: https://github.com/advisories?query=underscore - Run `npm i npm@latest -g` to upgrade your pm version, and then `npm audit` to get more info. +c:\users\copilot\.num\versions\node\v8.11.2\bin\local-ss1-proxy +-> +C:\Users\copilot\.nvm\versions\node\v8.11.2\bin\node_modules\local-ssl-proxy\bin\local-ssl-proxy + local-ss1-proxv@1.3.0 +added 18 packages in 3.321s + +copilot@DESKTOP-CEFAE6N MINGW64 ~ +$ local-ss1-proxy -n local.topcoder-dev.com -s 443 -t 3000 +Started proxy: https://local.topcoder-dev.com: 443 -> http://local.topcoder-dev.com:3000 +``` + +### Validation + +To validate, we'll run Chrome without web security to avoid it complaining about the local proxy redirects. It will redirect you the login page where you can use the sample test user to login. + +* Sample test user: `jcori` / `Appirio123` diff --git a/src/apps/admin/src/skills-manager/components/skill-modals/SkillModal.module.scss b/src/apps/dev-center/src/dev-center-pages/work-manager/WorkManagerGuide.module.scss similarity index 100% rename from src/apps/admin/src/skills-manager/components/skill-modals/SkillModal.module.scss rename to src/apps/dev-center/src/dev-center-pages/work-manager/WorkManagerGuide.module.scss diff --git a/src/apps/dev-center/src/dev-center-pages/work-manager/WorkManagerGuide.tsx b/src/apps/dev-center/src/dev-center-pages/work-manager/WorkManagerGuide.tsx new file mode 100644 index 000000000..605ebc16c --- /dev/null +++ b/src/apps/dev-center/src/dev-center-pages/work-manager/WorkManagerGuide.tsx @@ -0,0 +1,33 @@ +import * as React from 'react' + +import { Breadcrumb, BreadcrumbItemModel, ContentLayout } from '~/libs/ui' + +import { rootRoute, toolTitle } from '../../dev-center.routes' +import { LayoutDocHeader, MarkdownDoc } from '../../dev-center-lib/MarkdownDoc' +import useMarkdown from '../../dev-center-lib/hooks/useMarkdown' + +import styles from './WorkManagerGuide.module.scss' +import workManagerMarkdown from './WorkManager.md' + +export const WorkManagerGuide: React.FC = () => { + const { doc, toc, title }: ReturnType = useMarkdown({ uri: workManagerMarkdown }) + const breadcrumb: Array = React.useMemo(() => [ + { name: toolTitle, url: rootRoute || '/' }, + { name: title, url: '#' }, + ], [title]) + + return ( + + + + + + + ) +} + +export default WorkManagerGuide diff --git a/src/apps/dev-center/src/dev-center.routes.tsx b/src/apps/dev-center/src/dev-center.routes.tsx index a8be4c0c4..1ed2c1f97 100644 --- a/src/apps/dev-center/src/dev-center.routes.tsx +++ b/src/apps/dev-center/src/dev-center.routes.tsx @@ -4,9 +4,15 @@ import { lazyLoad, LazyLoadedComponent, PlatformRoute } from '~/libs/core' const Storybook: LazyLoadedComponent = lazyLoad(() => import('./dev-center-pages/platform-ui-app/storybook/Storybook')) +const PlatformUIGettingStarted: LazyLoadedComponent + = lazyLoad(() => import('./dev-center-pages/platform-ui-app/getting-started/GettingStartedGuide')) + const GettingStartedGuide: LazyLoadedComponent = lazyLoad(() => import('./dev-center-pages/community-app/getting-started/GettingStartedGuide')) +const WorkManagerGuide: LazyLoadedComponent + = lazyLoad(() => import('./dev-center-pages/work-manager/WorkManagerGuide')) + const DevCenterLandingPage: LazyLoadedComponent = lazyLoad(() => import('./dev-center-pages/community-app/landing-page/DevCenterLandingPage')) @@ -24,10 +30,18 @@ export const devCenterRoutes: ReadonlyArray = [ element: , route: '/storybook', }, + { + element: , + route: '/platform-ui', + }, { element: , route: '/getting-started', }, + { + element: , + route: '/work-manager-guide', + }, { element: , route: '/', diff --git a/src/apps/earn/README.md b/src/apps/earn/README.md deleted file mode 100644 index 6cf398fdf..000000000 --- a/src/apps/earn/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Instructions for Running the Earn Tool Locally - -## Earn Config \ No newline at end of file diff --git a/src/apps/earn/index.ts b/src/apps/earn/index.ts deleted file mode 100644 index 6f39cd49b..000000000 --- a/src/apps/earn/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './src' diff --git a/src/apps/earn/src/EarnApp.tsx b/src/apps/earn/src/EarnApp.tsx deleted file mode 100644 index d25ffcfa0..000000000 --- a/src/apps/earn/src/EarnApp.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { FC, useContext } from 'react' -import { Outlet, Routes } from 'react-router-dom' -import { Provider } from 'react-redux' - -import { routerContext, RouterContextData } from '~/libs/core' - -import { toolTitle } from './earn.routes' -import store from './store' - -const EarnApp: FC<{}> = () => { - - const { getChildRoutes }: RouterContextData = useContext(routerContext) - - return ( - - - - {getChildRoutes(toolTitle)} - - - ) -} - -export default EarnApp diff --git a/src/apps/earn/src/actions/auth.js b/src/apps/earn/src/actions/auth.js deleted file mode 100644 index 45cec7a91..000000000 --- a/src/apps/earn/src/actions/auth.js +++ /dev/null @@ -1,111 +0,0 @@ -/** - * @module "actions.auth" - * @desc Actions related to Topcoder authentication system. - */ - -import { createActions } from "redux-actions"; -import _ from "lodash"; -import { decodeToken, readCookie } from "../utils/token"; -import { getApiV3, getApiV5 } from "../services/challenge-api"; -import { setErrorIcon, ERROR_ICON_TYPES } from "../utils/errors"; -import { TOKEN_COOKIE_KEYS } from "../constants/index"; -import { getAuthUserTokens } from "../utils/auth"; - -/** - * Helper method that checks for HTTP error response v5 and throws Error in this case. - * @param {Object} res HTTP response object - * @return {Object} API JSON response object - * @private - */ -async function checkErrorV5(res) { - if (!res.ok) { - if (res.status === 403) { - setErrorIcon(ERROR_ICON_TYPES.API, "Auth0", res.statusText); - } - throw new Error(res.statusText); - } - const jsonRes = await res.json(); - if (jsonRes.message) { - throw new Error(res.message); - } - return { - result: jsonRes, - headers: res.headers, - }; -} - -/** - * @static - * @desc Creates an action that loads Topcoder user profile from v3 API. - * @param {String} userTokenV3 v3 authentication token. - * @return {Action} - */ -function loadProfileDone(userTokenV3) { - if (!userTokenV3) return Promise.resolve(null); - const user = decodeToken(userTokenV3); - const apiV3 = getApiV3(userTokenV3); - const apiV5 = getApiV5(userTokenV3); - return Promise.all([ - apiV3 - .get(`/members/${user.handle}`) - .then((res) => res.json()) - .then((res) => (res.result.status === 200 ? res.result.content : {})), - apiV5 - .get(`/groups?memberId=${user.userId}&membershipType=user`) - .then(checkErrorV5) - .then((res) => res.result || []), - ]).then(([profile, groups]) => ({ ...profile, groups })); -} - -/** - * @static - * @desc Creates an action that sets Topcoder v2 authentication token. - * @param {String} tokenV2 Topcoder v2 authentication token. - * @return {Action} - */ -function setTcTokenV2(tokenV2) { - return tokenV2; -} - -/** - * @static - * @desc Creates an action that decodes Topcoder v3 authentication token, - * to get user object, and then writes both the token and the user object into - * Redux store. - * @param {String} tokenV3 Topcoder v3 authentication token. - * @return {Action} - */ -function setTcTokenV3(tokenV3) { - return tokenV3; -} - -async function setAuthDone() { - const { tokenV3 } = await getAuthUserTokens(); - const user = tokenV3 ? decodeToken(tokenV3) : null; - return user; -} - -/** - * @static - * @desc Check token cookies to find if a user is logged out: - * This is because all the token cookies are cleared if a user is logged out. - * @return {Action} - */ -function checkIsLoggedOut() { - const tokenKeys = Object.keys(TOKEN_COOKIE_KEYS); - const isLoggedOut = _.every( - tokenKeys, - (k) => readCookie(TOKEN_COOKIE_KEYS[k]) === undefined - ); - return { isLoggedOut }; -} - -export default createActions({ - AUTH: { - LOAD_PROFILE: loadProfileDone, - SET_TC_TOKEN_V2: setTcTokenV2, - SET_TC_TOKEN_V3: setTcTokenV3, - SET_AUTH_DONE: setAuthDone, - CHECK_IS_LOGGED_OUT: checkIsLoggedOut, - }, -}); diff --git a/src/apps/earn/src/actions/challenge-listing/filter-panel.js b/src/apps/earn/src/actions/challenge-listing/filter-panel.js deleted file mode 100644 index 8f907f5ae..000000000 --- a/src/apps/earn/src/actions/challenge-listing/filter-panel.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Actions related to the header filter panel. - */ - - import _ from 'lodash'; - import { createActions } from 'redux-actions'; - - export default createActions({ - CHALLENGE_LISTING: { - FILTER_PANEL: { - /* Expands / collapses the filter panel. */ - SET_EXPANDED: _.identity, - - /* Updates text in the search bar, without applying it to the active - * challenge filter. The text will be set to the filter when Enter is - * pressed. */ - SET_SEARCH_TEXT: _.identity, - - /* Shows / hides the modal with track switches (for mobile view only). */ - SHOW_TRACK_MODAL: _.identity, - }, - }, - }); - \ No newline at end of file diff --git a/src/apps/earn/src/actions/challenge-listing/index.js b/src/apps/earn/src/actions/challenge-listing/index.js deleted file mode 100644 index a878ba764..000000000 --- a/src/apps/earn/src/actions/challenge-listing/index.js +++ /dev/null @@ -1,612 +0,0 @@ -/** - * Challenge listing actions. - */ - - import _ from 'lodash'; - import { createActions } from 'redux-actions'; - import { decodeToken } from "@earn/utils/token"; - import { processSRM } from '@earn/utils/tc'; - import { getService } from '@earn/services/challenges'; - import { getReviewOpportunitiesService } from '@earn/services/reviewOpportunities'; - import { BUCKETS } from '@earn/utils/challenge-listing/buckets'; - import SORT from '@earn/utils/challenge-listing/sort'; - import { fireErrorMessage } from "@earn/utils/errors"; - -/** - * The maximum number of challenges to fetch in a single API call. - */ -const PAGE_SIZE = 10; - -/** - * The maximum number of review opportunities to fetch in a single API call. - */ -const REVIEW_OPPORTUNITY_PAGE_SIZE = 1000; - -/** - * Private. Loads from the backend all challenges matching some conditions. - * @param {Function} getter Given params object of shape { limit, offset } - * loads from the backend at most "limit" challenges, skipping the first - * "offset" ones. Returns loaded challenges as an array. - * @param {Number} page Optional. Next page of challenges to load. - * @param {Array} prev Optional. Challenges loaded so far. - */ -// function getAll(getter, page = 0, prev) { -// /* Amount of challenges to fetch in one API call. 50 is the current maximum -// * amount of challenges the backend returns, event when the larger limit is -// * explicitely required. */ - -// return getter({ -// perPage: PAGE_SIZE, -// page: page + 1, -// }).then(({ challenges: chunk }) => { -// if (!chunk.length) return prev || []; -// return getAll(getter, 1 + page, prev ? prev.concat(chunk) : chunk); -// }); -// } - -function getChallengeTypesInit() {} -/** - * Gets possible challenge types. - * @return {Promise} - */ -function getChallengeTypesDone() { - return getService() - .getChallengeTypes() - .then(res => res.sort((a, b) => a.name.localeCompare(b.name))); -} - -/** - * Gets possible challenge tags (technologies). - * @return {Promise} - */ -function getChallengeTagsDone() { - return getService() - .getChallengeTags() - .then(res => res.map(item => item.name) - .sort((a, b) => a.localeCompare(b))); -} - -/** - * Notifies about reloading of all active challenges. The UUID is stored in the - * state, and only challenges fetched by getAllActiveChallengesDone action with - * the same UUID will be accepted into the state. - * @param {String} uuid - * @return {String} - */ -function getActiveChallengesInit(uuid, page, frontFilter) { - return { uuid, page, frontFilter }; -} - -function getOpenForRegistrationChallengesInit(uuid, page, frontFilter) { - return { uuid, page, frontFilter }; -} - -function getMyChallengesInit(uuid, page, frontFilter) { - return { uuid, page, frontFilter }; -} - -function getAllChallengesInit(uuid, page, frontFilter) { - return { uuid, page, frontFilter }; -} - -function getMyPastChallengesInit(uuid, page, frontFilter) { - return { uuid, page, frontFilter }; -} - -/** - * Get all challenges and match with user challenges - * @param {String} uuid progress id - * @param {String} tokenV3 token v3 - * @param {Object} filter filter object - * @param {number} page start page - */ -// function getAllActiveChallengesWithUsersDone(uuid, tokenV3, filter, page = 0) { -// const service = getService(tokenV3); -// const calls = [ -// getAll(params => service.getChallenges(filter, params), page), -// ]; -// let user; -// if (tokenV3) { -// user = decodeToken(tokenV3).userId; - -// const newFilter = _.mapKeys(filter, (value, key) => { -// if (key === 'tag') return 'technologies'; - -// return key; -// }); - -// // Handle any errors on this endpoint so that the non-user specific challenges -// // will still be loaded. -// calls.push(getAll(params => service.getUserChallenges(user, newFilter, params) -// .catch(() => ({ challenges: [] }))), page); -// } -// return Promise.all(calls).then(([ch, uch]) => { -// /* uch array contains challenges where the user is participating in -// @@ -111,8 +124,8 @@ function getAllActiveChallengesDone(uuid, tokenV3) { -// * challenges in an efficient way. */ -// if (uch) { -// const map = {}; -// uch.forEach((item) => { map[item.id] = item; }); -// ch.forEach((item) => { -// if (map[item.id]) { -// /* It is fine to reassing, as the array we modifying is created just -// * above within the same function. */ -// /* eslint-disable no-param-reassign */ -// item.users[user] = true; -// item.userDetails = map[item.id].userDetails; -// /* eslint-enable no-param-reassign */ -// } -// }); -// } - -// return { uuid, challenges: ch, ...filter }; -// }); -// } - -/** TODO: Inspect if the 2 actions bellow can be removed? - * They do duplicate what is done in `getActiveChallengesDone` but fetch all challenges - * which was refactored in listing-improve - */ -// function getAllActiveChallengesInit(uuid) { -// return uuid; -// } -// function getAllActiveChallengesDone(uuid, tokenV3) { -// const filter = { status: 'Active' }; -// return getAllActiveChallengesWithUsersDone(uuid, tokenV3, filter); -// } - -// function getAllUserChallengesInit(uuid) { -// return uuid; -// } - -// function getAllUserChallengesDone(uuid, tokenV3) { -// const memberId = decodeToken(tokenV3).userId; -// const filter = { status: 'Active', memberId }; -// return getAllActiveChallengesWithUsersDone(uuid, tokenV3, filter); -// } - -/** - * Gets 1 page of active challenges (including marathon matches) from the backend. - * Once this action is completed any active challenges saved to the state before - * will be dropped, and the newly fetched ones will be stored there. - * Loading of all challenges wil start in background. - * @param {String} uuid - * @param {Number} page - * @param {Object} backendFilter Backend filter to use. - * @param {String} tokenV3 Optional. Topcoder auth token v3. Without token only - * public challenges will be fetched. With the token provided, the action will - * also fetch private challenges related to this user. - * @param {Object} frontFilter - - * @return {Promise} - */ -function getActiveChallengesDone(uuid, page, backendFilter, tokenV3, frontFilter = {}) { - const { sorts } = frontFilter; - const sortObj = SORT[sorts[BUCKETS.ONGOING]]; - const filter = { - backendFilter, - frontFilter: { - ...frontFilter, - status: 'Active', - currentPhaseName: 'Submission', - registrationEndDateEnd: new Date().toISOString(), - perPage: PAGE_SIZE, - page: page + 1, - sortBy: sortObj.field ? sortObj.field : sorts[BUCKETS.ONGOING], - sortOrder: sortObj.order, - }, - }; - delete filter.frontFilter.sorts; - const service = getService(tokenV3); - return service.getChallenges(filter).then(ch => ({ - uuid, - challenges: ch.challenges, - meta: ch.meta, - frontFilter, - })); - // const calls = [ - // service.getChallenges(filter, { - // perPage: PAGE_SIZE, - // page: page + 1, - // }), - // ]; - // let user; - // if (tokenV3) { - // user = decodeToken(tokenV3).userId; - - // // Handle any errors on this endpoint so that the non-user specific challenges - // // will still be loaded. - // calls.push(service.getUserChallenges(user, filter, {}) - // .catch(() => ({ challenges: [] }))); - // } - // return Promise.all(calls).then(([ch]) => ({ - // uuid, - // challenges: ch.challenges, - // meta: ch.meta, - // frontFilter, - // })); -} - -/** - * Gets open for registration challenges - * @param {String} uuid - * @param {Number} page - * @param {Object} backendFilter Backend filter to use. - * @param {String} tokenV3 Optional. Topcoder auth token v3. Without token only - * public challenges will be fetched. With the token provided, the action will - * also fetch private challenges related to this user. - * @param {Object} frontFilter - * @param {boolean} recommended recommended toggle is on or off - * @param {String} handle user handle - - * @return {Promise} - */ -function getOpenForRegistrationChallengesDone(uuid, page, backendFilter, - tokenV3, frontFilter = {}, recommended = false, handle) { - const { sorts } = frontFilter; - const sortObj = SORT[sorts[BUCKETS.OPEN_FOR_REGISTRATION]]; - const filter = { - backendFilter, - frontFilter: { - ...frontFilter, - status: 'Active', - currentPhaseName: 'Registration', - perPage: PAGE_SIZE, - page: page + 1, - sortBy: sortObj && sortObj.field ? sortObj.field : sorts[BUCKETS.OPEN_FOR_REGISTRATION], - sortOrder: sortObj ? sortObj.order : 'asc', - }, - }; - delete filter.frontFilter.sorts; - const service = getService(tokenV3); - if (recommended) { - return service.getRecommendedChallenges(filter, handle).then(ch => ({ - uuid, - openForRegistrationChallenges: ch.challenges, - meta: ch.meta, - frontFilter, - })); - } - - return service.getChallenges(filter).then(ch => ({ - uuid, - openForRegistrationChallenges: ch.challenges, - meta: ch.meta, - frontFilter, - })); -} - -function getMyChallengesDone(uuid, page, backendFilter, tokenV3, frontFilter = {}) { - const userId = decodeToken(tokenV3).userId.toString(); - const { sorts } = frontFilter; - const sortObj = SORT[sorts[BUCKETS.MY]]; - const filter = { - backendFilter, - frontFilter: { - ...frontFilter, - status: 'Active', - memberId: userId, - perPage: PAGE_SIZE, - page: page + 1, - sortBy: sortObj.field ? sortObj.field : sorts[BUCKETS.MY], - sortOrder: sortObj.order, - }, - }; - delete filter.frontFilter.sorts; - const service = getService(tokenV3); - return service.getChallenges(filter).then(ch => ({ - uuid, - myChallenges: ch.challenges, - meta: ch.meta, - frontFilter, - })); -} - -function getAllChallengesDone(uuid, page, backendFilter, tokenV3, frontFilter = {}) { - const { sorts } = frontFilter; - const sortObj = SORT[sorts[BUCKETS.ALL]]; - const filter = { - backendFilter, - frontFilter: { - ...frontFilter, - status: 'Active', - perPage: PAGE_SIZE, - page: page + 1, - sortBy: sortObj.field ? sortObj.field : sorts[BUCKETS.ALL], - sortOrder: sortObj.order, - }, - }; - delete filter.frontFilter.sorts; - const service = getService(tokenV3); - return service.getChallenges(filter).then(ch => ({ - uuid, - allChallenges: ch.challenges, - meta: ch.meta, - frontFilter, - })); -} - -function getMyPastChallengesDone(uuid, page, backendFilter, tokenV3, frontFilter = {}) { - const userId = decodeToken(tokenV3).userId.toString(); - const { sorts } = frontFilter; - const sortObj = SORT[sorts[BUCKETS.MY_PAST]]; - const filter = { - backendFilter, - frontFilter: { - ...frontFilter, - status: 'Completed', - memberId: userId, - perPage: PAGE_SIZE, - page: page + 1, - sortBy: sortObj.field ? sortObj.field : sorts[BUCKETS.MY_PAST], - sortOrder: sortObj.order, - }, - }; - delete filter.frontFilter.sorts; - const service = getService(tokenV3); - return service.getChallenges(filter).then(ch => ({ - uuid, - myPastChallenges: ch.challenges, - meta: ch.meta, - frontFilter, - })); -} - -function getTotalChallengesCountInit(uuid) { - return { uuid }; -} - -function getTotalChallengesCountDone(uuid, tokenV3, frontFilter = {}) { - const filter = { - backendFilter: {}, - frontFilter: { - ...frontFilter, - status: 'Active', - isLightweight: true, - perPage: 1, - }, - }; - delete filter.frontFilter.sorts; - const service = getService(tokenV3); - return service.getChallenges(filter).then(ch => ({ - uuid, - meta: ch.meta, - })); -} - -/** - * Init loading of all challenges - * @param {String} uuid - */ -// function getRestActiveChallengesInit(uuid) { -// return { uuid }; -// } - -/** - * Loading all challenges - * @param {String} uuid progress id - * @param {String} tokenV3 token v3 - */ -// function getRestActiveChallengesDone(uuid, tokenV3, filter) { -// const mergedFilter = { -// ...filter, -// status: 'Active', -// }; -// return getAllActiveChallengesWithUsersDone(uuid, tokenV3, mergedFilter, 1); -// } - -/** - * Prepare for getting all recommended challenges - * @param {String} uuid progress id - */ -// function getAllRecommendedChallengesInit(uuid) { -// return uuid; -// } -/** - * Get all recommended challenges - * @param {String} uuid progress id - * @param {String} tokenV3 token v3 - * @param {*} recommendedTags recommended tags - */ -// function getAllRecommendedChallengesDone(uuid, tokenV3, recommendedTags) { -// const filter = { -// status: 'Active', -// ...(!_.isEmpty(recommendedTags) && { tag: recommendedTags }), -// }; -// return getAllActiveChallengesWithUsersDone(uuid, tokenV3, filter); -// } - -/** - * Notifies the state that we are about to load the specified page of past - * challenges. - * @param {String} uuid - * @param {Number} page - * @param {Object} frontFilter - * @return {Object} - */ -function getPastChallengesInit(uuid, page, frontFilter) { - return { uuid, page, frontFilter }; -} - -/** - * Gets the specified page of past challenges (including MMs). - * @param {Number} page Page of challenges to fetch. - * @param {Object} filter Backend filter to use. - * @param {String} tokenV3 Optional. Topcoder auth token v3. - * @param {Object} frontFilter Optional. Original frontend filter. - * @param {Object} - */ -function getPastChallengesDone(uuid, page, backendFilter, tokenV3, frontFilter = {}) { - const { sorts } = frontFilter; - const sortObj = SORT[sorts[BUCKETS.ALL_PAST]]; - const filter = { - backendFilter, - frontFilter: { - ...frontFilter, - status: 'Completed', - perPage: PAGE_SIZE, - page: page + 1, - sortBy: sortObj.field ? sortObj.field : sorts[BUCKETS.ALL_PAST], - sortOrder: sortObj.order, - }, - }; - delete filter.frontFilter.sorts; - const service = getService(tokenV3); - return service.getChallenges(filter).then(ch => ({ - uuid, - pastChallenges: ch.challenges, - frontFilter, - meta: ch.meta, - })); -} - -/** - * Action to get a list of currently open Review Opportunities using V3 API - * @param {String} uuid Unique identifier for init/donen instance from shortid module - * @param {Number} page Page of review opportunities to fetch. - * @param {String} tokenV3 Optional. Topcoder auth token v3. - * @return {Object} Action object - */ -function getReviewOpportunitiesDone(uuid, page, tokenV3) { - return getReviewOpportunitiesService(tokenV3) - .getReviewOpportunities(REVIEW_OPPORTUNITY_PAGE_SIZE, page * REVIEW_OPPORTUNITY_PAGE_SIZE) - .then(loaded => ({ uuid, loaded })) - .catch((error) => { - fireErrorMessage('Error Getting Review Opportunities', error.content || error); - return Promise.reject(error); - }); -} - -/** - * Payload creator for the action that inits the loading of SRMs. - * @param {String} uuid - * @return {String} - */ -function getSrmsInit(uuid) { - return uuid; -} - -/** - * Payload creator for the action that loads SRMs. - * @param {String} uuid - * @param {String} handle - * @param {Object} params - * @param {String} tokenV3 - */ -function getSrmsDone(uuid, handle, params, tokenV3) { - const service = getService(tokenV3); - const promises = [service.getSrms(params)]; - if (handle) { - promises.push(service.getUserSrms(handle, params)); - } - return Promise.all(promises).then((data) => { - let srms = data[0]; - const userSrms = data[1]; - const userSrmsMap = {}; - _.forEach(userSrms, (srm) => { - userSrmsMap[srm.id] = srm; - }); - srms = _.map(srms, (srm) => { - if (userSrmsMap[srm.id]) { - return processSRM(srm); - } - return srm; - }); - return { uuid, data: srms }; - }); -} - -/** - * Payload creator for the action that initialize user registered challenges. - * @param {String} uuid - * @return {String} -// */ -// function getUserChallengesInit(uuid) { -// return { uuid }; -// } - -// /** -// * Payload creator for the action that loads user registered challenges. -// * @param {String} userId -// * @return {String} -// */ -// function getUserChallengesDone(userId, tokenV3) { -// const service = getService(tokenV3); - -// return service.getUserResources(userId) -// .then(item => item) -// .catch((error) => { -// fireErrorMessage('Error Getting User Challenges', error.content || error); -// return Promise.reject(error); -// }); -// } - -export default createActions({ - CHALLENGE_LISTING: { - DROP_CHALLENGES: _.noop, - DROP_ACTIVE_CHALLENGES: _.noop, - DROP_OPEN_FOR_REGISTRATION_CHALLENGES: _.noop, - DROP_MY_CHALLENGES: _.noop, - DROP_ALL_CHALLENGES: _.noop, - DROP_PAST_CHALLENGES: _.noop, - DROP_MY_PAST_CHALLENGES: _.noop, - DROP_RECOMMENDED_CHALLENGES: _.noop, - - // GET_ALL_ACTIVE_CHALLENGES_INIT: getAllActiveChallengesInit, - // GET_ALL_ACTIVE_CHALLENGES_DONE: getAllActiveChallengesDone, - - // GET_ALL_USER_CHALLENGES_INIT: getAllUserChallengesInit, - // GET_ALL_USER_CHALLENGES_DONE: getAllUserChallengesDone, - - // GET_ALL_RECOMMENDED_CHALLENGES_INIT: getAllRecommendedChallengesInit, - // GET_ALL_RECOMMENDED_CHALLENGES_DONE: getAllRecommendedChallengesDone, - - GET_ALL_CHALLENGES_INIT: getAllChallengesInit, - GET_ALL_CHALLENGES_DONE: getAllChallengesDone, - - GET_ACTIVE_CHALLENGES_INIT: getActiveChallengesInit, - GET_ACTIVE_CHALLENGES_DONE: getActiveChallengesDone, - - GET_OPEN_FOR_REGISTRATION_CHALLENGES_INIT: getOpenForRegistrationChallengesInit, - GET_OPEN_FOR_REGISTRATION_CHALLENGES_DONE: getOpenForRegistrationChallengesDone, - - GET_MY_CHALLENGES_INIT: getMyChallengesInit, - GET_MY_CHALLENGES_DONE: getMyChallengesDone, - - GET_MY_PAST_CHALLENGES_INIT: getMyPastChallengesInit, - GET_MY_PAST_CHALLENGES_DONE: getMyPastChallengesDone, - - // GET_REST_ACTIVE_CHALLENGES_INIT: getRestActiveChallengesInit, - // GET_REST_ACTIVE_CHALLENGES_DONE: getRestActiveChallengesDone, - - GET_CHALLENGE_TYPES_INIT: getChallengeTypesInit, - GET_CHALLENGE_TYPES_DONE: getChallengeTypesDone, - - GET_TOTAL_CHALLENGES_COUNT_INIT: getTotalChallengesCountInit, - GET_TOTAL_CHALLENGES_COUNT_DONE: getTotalChallengesCountDone, - - GET_CHALLENGE_TAGS_INIT: _.noop, - GET_CHALLENGE_TAGS_DONE: getChallengeTagsDone, - - GET_PAST_CHALLENGES_INIT: getPastChallengesInit, - GET_PAST_CHALLENGES_DONE: getPastChallengesDone, - - GET_REVIEW_OPPORTUNITIES_INIT: (uuid, page) => ({ uuid, page }), - GET_REVIEW_OPPORTUNITIES_DONE: getReviewOpportunitiesDone, - - GET_SRMS_INIT: getSrmsInit, - GET_SRMS_DONE: getSrmsDone, - - // GET_USER_CHALLENGES_INIT: getUserChallengesInit, - // GET_USER_CHALLENGES_DONE: getUserChallengesDone, - - EXPAND_TAG: id => id, - - /* Pass in community ID. */ - SELECT_COMMUNITY: _.identity, - - SET_FILTER: _.identity, - - SET_SORT: (bucket, sort) => ({ bucket, sort }), - }, -}); diff --git a/src/apps/earn/src/actions/challenge-listing/sidebar.js b/src/apps/earn/src/actions/challenge-listing/sidebar.js deleted file mode 100644 index cc7d6dbca..000000000 --- a/src/apps/earn/src/actions/challenge-listing/sidebar.js +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Actions for the sidebar. - */ - - import _ from 'lodash'; - import { createActions } from 'redux-actions'; - // import { services } from 'topcoder-react-lib'; - - // const { getUserSettingsService } = services.userSetting; - - /** - * Changes name of the specified filter (but does not save it to the backend). - * @param {String} index - * @param {String} name - */ - // function changeFilterName(index, name) { - // return { index, name }; - // } - - /** - * Deletes saved filter. - * @param {String} id - * @param {Object} tokenV2 - * @return {Promise} - */ - // function deleteSavedFilter(id, tokenV2) { - // return getUserSettingsService(tokenV2) - // .deleteFilter(id).then(() => id); - // } - - /** - * Handles drag move event. - * @param {Object} dragEvent ReactJS onDrag event. - * @param {Object} dragState - * - * NOTE: This code is just taken from the previous version of the code. It has] - * some flaws, but it is not the main problem for now. - * - * NOTE: This implementation of dragging has a flaw: if you take an item and - * drug it down, you'll see that it is correctly moved down the list, but its - * highlighting (at least in Chrome) remains in the original position. Compare - * to the situation, when you drag an item upward the list: the highlighting - * moves properly with the item. This is related to the way ReactJS interacts - * with DOM, and, most probably, it is just easier to adopt some 3-rd party - * Drag-n-Drop library, then to find out a work-around. - */ - // function dragSavedFilterMove(dragEvent, dragState) { - /* For a reason not clear to me, shortly after starting to drag a filter, - * and also when the user releases the mouse button, thus ending the drag, - * this handler gets an event with 'screenY' position equal 0. This breaks - * the dragging handling, which works just fine otherwise. Hence, this simple - * fix of the issue, until the real problem is figured out. - */ - // if (!dragEvent.screenY) return dragState; - - // /* Calculation of the target position of the dragged item inside the filters - // * array. */ - // const shift = (dragEvent.screenY - dragState.y) / dragEvent.target.offsetHeight; - // const index = Math.round(dragState.startIndex + shift); - // if (index === dragState.index) return dragState; - // return { ...dragState, currentIndex: index }; - // } - - /** - * Initializes drag of a filter item. - * @param {Number} index - * @param {Object} dragEvent - * @return {Object} - */ - // function dragSavedFilterStart(index, dragEvent) { - // return { - // currentIndex: index, - // startIndex: index, - // y: dragEvent.screenY, - // }; - // } - - // function getSavedFilters(tokenV2) { - // return getUserSettingsService(tokenV2).getFilters(); - // } - - /** - * After changing filter name with changeFilterName(..) this action can be used - * to reset filter name to the one last saved into API. No API call is made, - * as the last saved name is kept inside the state. - * @param {String} index - */ - // function resetFilterName(index) { - // return index; - // } - - /** - * Saves filter to the backend. - * @param {String} name - * @param {Object} filter Filter state. - * @param {String} tokenV2 - * @return {Promise} - */ - // function saveFilter(name, filter, tokenV2) { - // return getUserSettingsService(tokenV2) - // .saveFilter(name, filter); - // } - - /** - * Updates all saved filters (basically to update their ordering in the - * backend). - * @param {Array} savedFilters - * @param {String} tokenV2 - */ - // function updateAllSavedFilters(savedFilters, tokenV2) { - // const service = getUserSettingsService(tokenV2); - // savedFilters.forEach(filter => service.updateFilter(filter.id, filter.name, filter.filter)); - // } - - /** - * Saves updated fitler to the backend. - * @param {Object} filter - * @param {String} tokenV2 - * @return {Promise} - */ - // function updateSavedFilter(filter, tokenV2) { - // return getUserSettingsService(tokenV2) - // .updateFilter(filter.id, filter.name, filter.filter); - // } - - export default createActions({ - CHALLENGE_LISTING: { - SIDEBAR: { - // CHANGE_FILTER_NAME: changeFilterName, - - // DELETE_SAVED_FILTER: deleteSavedFilter, - - // DRAG_SAVED_FILTER_MOVE: dragSavedFilterMove, - // DRAG_SAVED_FILTER_START: dragSavedFilterStart, - - // GET_SAVED_FILTERS: getSavedFilters, - - // RESET_FILTER_NAME: resetFilterName, - - // SAVE_FILTER_DONE: saveFilter, - - // SAVE_FILTER_INIT: _.noop, - - /* Pass in the bucket type. */ - SELECT_BUCKET: (bucket, expanding = false) => ({ bucket, expanding }), - SELECT_BUCKET_DONE: _.noop, - - /* Pass in the index of filter inside savedFilters array. */ - // SELECT_SAVED_FILTER: _.identity, - - /* Pass in true/false to enable/disable. */ - // SET_EDIT_SAVED_FILTERS_MODE: _.identity, - - // UPDATE_ALL_SAVED_FILTERS: updateAllSavedFilters, - // UPDATE_SAVED_FILTER: updateSavedFilter, - }, - }, - }); - \ No newline at end of file diff --git a/src/apps/earn/src/actions/challenge.js b/src/apps/earn/src/actions/challenge.js deleted file mode 100644 index bb0770780..000000000 --- a/src/apps/earn/src/actions/challenge.js +++ /dev/null @@ -1,442 +0,0 @@ -/** - * @module "actions.challenge" - * @desc Actions related to Topcoder challenges APIs. - */ - -import _ from "lodash"; -import config from "../config"; -import { createActions } from "redux-actions"; -import { decodeToken } from "../utils/token"; -import { getService as getChallengesService } from "../services/challenges"; -import { getService as getSubmissionService } from "../services/submissions"; -import { getApi } from "../services/challenge-api"; -import * as submissionUtil from "../utils/submission"; -import challenge from "../reducers/challenge"; - -const { PAGE_SIZE } = config; - -/** - * Private. Loads from the backend all data matching some conditions. - * @param {Function} getter Given params object of shape { limit, offset } - * loads from the backend at most "limit" data, skipping the first - * "offset" ones. Returns loaded data as an array. - * @param {Number} page Optional. Next page of data to load. - * @param {Number} perPage Optional. The size of the page content to load. - * @param {Array} prev Optional. data loaded so far. - */ -function getAll(getter, page = 1, perPage = PAGE_SIZE, prev) { - /* Amount of submissions to fetch in one API call. 50 is the current maximum - * amount of submissions the backend returns, event when the larger limit is - * explicitely required. */ - return getter({ - page, - perPage, - }).then((res) => { - if (res.length === 0) { - return prev || res; - } - // parse submissions - let current = []; - if (prev) { - current = prev.concat(res); - } else { - current = res; - } - return getAll(getter, 1 + page, perPage, current); - }); -} - -/** - * @static - * @desc Creates an action that drops from Redux store all checkpoints loaded - * before. - * @return {Action} - */ -function dropCheckpoints() {} - -/** - * @static - * @desc Creates an action that drops from Redux store all challenge results - * loaded before. - * @return {Action} - */ -function dropResults() {} - -/** - * @static - * @desc Creates an action that signals beginning of challenge details loading. - * @param {Number|String} challengeId Challenge ID - * @return {Action} - */ -function getDetailsInit(challengeId) { - return _.toString(challengeId); -} - -/** - * @static - * @desc Creates an action that loads challenge details. - * @param {Number|String} challengeId Challenge ID. - * @param {String} tokenV3 Topcoder v3 auth token. - * @param {String} tokenV2 Topcoder v2 auth token. - * @return {Action} - */ -function getDetailsDone({challengeId, tokenV3, tokenV2}) { - const service = getChallengesService(tokenV3, tokenV2); - const v3Promise = service.getChallengeDetails(challengeId); - return v3Promise; -} - -/** - * @static - * @desc Creates an action that signals beginning of user submissions loading. - * @param {String} challengeId Challenge ID. - * @return {Action} - */ -function getSubmissionsInit(challengeId) { - /* As a safeguard, we enforce challengeId to be string (in case somebody - * passes in a number, by mistake). */ - return _.toString(challengeId); -} - -/** - * @static - * @desc Creates an action that loads user's submissions to the specified - * challenge. - * @param {String} challengeId Challenge ID. - * @param {String} tokenV3 Topcoder auth token v3. - * @return {Action} - */ -function getSubmissionsDone({challengeId, tokenV3}) { - const user = decodeToken(tokenV3); - const submissionsService = getSubmissionService(tokenV3); - const filters = { - challengeId, - memberId: user.userId, - }; - return submissionsService.getSubmissions(filters) - .then(submissions => ({ - challengeId: _.toString(challengeId), - submissions, - })) - .catch((error) => { - const err = { challengeId: _.toString(challengeId), error }; - throw err; - }); -} - -/** - * @static - * @desc Creates an action that signals beginning of Marathon Match submissions loading. - * @param {String} challengeId Challenge ID. - * @return {Action} - */ -function getMMSubmissionsInit(challengeId) { - /* As a safeguard, we enforce challengeId to be string (in case somebody - * passes in a number, by mistake). */ - return _.toString(challengeId); -} - - -/** - * @static - * @desc Creates an action that loads Marathon Match submissions to the specified - * challenge. - * @param {String} challengeId Challenge ID. - * @param {Array} registrants The array of register. - * @param {String} tokenV3 Topcoder auth token v3. - * @return {Action} - */ -function getMMSubmissionsDone(challengeId, tokenV3) { - const filter = { challengeId }; - const submissionsService = getSubmissionService(tokenV3); - - // TODO: Move those numbers to configs - return getAll(params => submissionsService.getSubmissions(filter, params), 1, 500) - .then((submissions) => { - const finalSubmissions = submissionUtil.processMMSubmissions(submissions); - return { - challengeId, - submissions: finalSubmissions, - tokenV3, - }; - }); -} - -/** - * @static - * @desc Creates an action that signals beginning of registration for a - * challenge. - * @return {Action} - */ -function registerInit() { -} - -/** - * @static - * @desc Creates an action that registers user for a challenge. - * @param {Object} auth An object that holds auth tokens. You can directly pass - * here the `auth` segment of Redux store. - * @param [auth.tokenV2]{String} Topcoder auth token v2. - * @param [auth.tokenV3]{String} Topcoder auth token v3. - * @param {String} challengeId Challenge ID. - * @return {Action} - */ -function registerDone(auth, challengeId) { - return getChallengesService(auth.tokenV3) - .register(challengeId) - /* As a part of registration flow we silently update challenge details, - * reusing for this purpose the corresponding action handler. */ - // Uses a delay to allow API time to update - .then(() => new Promise( - resolve => setTimeout( - () => resolve(getDetailsDone({challengeId, tokenV3: auth.tokenV3, tokenV2: auth.tokenV2})), - config.CHALLENGE_DETAILS_REFRESH_DELAY, - ), - )); -} - -/** - * @static - * @desc Creates an action that signals beginning of user unregistration from a - * challenge. - * @return {Action} - */ -function unregisterInit() {} - -/** - * @static - * @desc Creates an action that unregisters user from a challenge. - * @param {Object} auth Object that holds Topcoder auth tokens. - * @param {String} [auth.tokenV2] v2 token. - * @param {String} [auth.tokenV3] v3 token. - * @param {String} challengeId Challenge ID. - * @return {Action} - */ -function unregisterDone(auth, challengeId) { - return getChallengesService(auth.tokenV3) - .unregister(challengeId) - /* As a part of unregistration flow we silently update challenge details, - * reusing for this purpose the corresponding action handler. */ - // Uses a delay to allow API time to update - .then(() => new Promise( - resolve => setTimeout( - () => resolve(getDetailsDone({challengeId, tokenV3: auth.tokenV3, tokenV2: auth.tokenV2})), - config.CHALLENGE_DETAILS_REFRESH_DELAY, - ), - )); -} - -/** - * @static - * @desc Creates an action that signals beginning of challenge results loading. - * @param {Number|String} challengeId Challenge ID - * @return {Action} - */ -function loadResultsInit(challengeId) { - return _.toString(challengeId); -} - -/** - * @static - * @desc Creates an action that loads challenge results. - * @param {Object} auth Object that holds Topcoder auth tokens. - * @param {String} [auth.tokenV2] v2 token. - * @param {String} [auth.tokenV3] v3 token. - * @param {Number|String} challengeId Challenge ID. Should match the one passed - * in the previous {@link module:actions.challenge.loadResultsInit} call. - * @param {String} type Challenge type. - * @return {Action} - */ -function loadResultsDone(auth, challengeId, type) { - return getApi('V2') - .fetch(`/${type}/challenges/result/${challengeId}`) - .then(response => response.json()) - .then(response => ({ - challengeId: _.toString(challengeId), - results: response.results, - })); -} - -/** - * @static - * @desc Creates an action that signals beginning of challenge checkpoints data - * loading. - * @return {Action} - */ -function fetchCheckpointsInit() {} - -/** - * @static - * @desc Creates an action that loads challenge checkpoints data. - * @param {String} tokenV2 Topcoder v2 auth token. - * @param {String} challengeId Challenge ID. - */ -function fetchCheckpointsDone(tokenV2, challengeId) { - const endpoint = `/design/challenges/checkpoint/${challengeId}`; - return getApi('V2').fetch(endpoint) - .then((response) => { - if (response.status !== 200) { - throw response.status; - } else { - return response.json(); - } - }) - .then((response) => { - // Expanded key is used for UI expand/collapse. - response.checkpointResults.forEach((checkpoint, index) => { - response.checkpointResults[index].expanded = false; - }); - return { - challengeId: String(challengeId), - checkpoints: response, - }; - }) - .catch(error => ({ - error, - challengeId: String(challengeId), - })); -} - -/** - * @static - * @desc Creates an action that Toggles checkpoint details panel in the Topcoder - * Submission Management Page. - * @todo This is UI action relevant to a specific page in specific app. Must be - * moved back to Community App. - * @param {Number} id Checkpoint ID. - * @param {Boolean} open Target state: `true` to expand, `false` to collapse the - * details. - * @return {Action} - */ -function toggleCheckpointFeedback(id, open) { - return { id, open }; -} - -/** - * @static - * @desc Creates an action that signals beginning of challenge details update. - * @todo No idea, why we have this action. This functionality should be covered - * by {@link module:actions.challenge.getDetailsInit} and - * {@link module:actions.challenge.getDetailsDone}. We need to refactor this. - * @param {String} uuid UUID of the operation (the same should be passed into - * the corresponding {@link module:actions.challenge.updateChallengeDone}). - * @return {Action} - */ -function updateChallengeInit(uuid) { - return uuid; -} - -/** - * @static - * @desc Creates an action that updates challenge details. - * @todo No idea, why we have this action. This functionality should be covered - * by {@link module:actions.challenge.getDetailsInit} and - * {@link module:actions.challenge.getDetailsDone}. We need to refactor this. - * @param {String} uuid Operation UUID. Should match the one passed into the - * previous {@link module:actions.challenge.updateChallengeInit} call. - * @param {Object} challenge Challenge data. - * @param {String} tokenV3 Topcoder v3 auth token. - * @return {Action} - */ -function updateChallengeDone(uuid, challenge, tokenV3) { - return getChallengesService(tokenV3).updateChallenge(challenge) - .then(res => ({ uuid, res })); -} - -/** - * @static - * @desc Creates an action that signals beginning of getting count of user's active challenges. - * @return {Action} - */ -function getActiveChallengesCountInit() {} - -/** - * @static - * @desc Creates an action that gets count of user's active challenges from the backend. - * @param {String} handle Topcoder user handle. - * @param {String} tokenV3 Optional. Topcoder auth token v3. Without token only - * public challenges will be counted. With the token provided, the action will - * also count private challenges related to this user. - * @return {Action} - */ -function getActiveChallengesCountDone(handle, tokenV3) { - return getChallengesService(tokenV3).getActiveChallengesCount(handle); -} - -/** - * @static - * @desc Creates an action that gets submission information by submission id - * @param {String} submissionId The submission id - * @return {Action} - */ -function getSubmissionInformationInit(challengeId, submissionId) { - return { challengeId: _.toString(challengeId), submissionId: _.toString(submissionId) }; -} - -/** - * @static - * @desc Creates an action that gets submission information from the backend. - * @param {String} submissionId The submission id - * @param {String} tokenV3 Topcoder auth token v3. - * @return {Action} - */ -function getSubmissionInformationDone(challengeId, submissionId, tokenV3) { - const filter = { challengeId }; - const submissionsService = getSubmissionService(tokenV3); - - return getAll(params => submissionsService.getSubmissions(filter, params), 1, 500) - .then((submissions) => { - const submission = _.find(submissions, { id: submissionId }); - _.remove(submission.review, review => review.typeId === config.AV_SCAN_SCORER_REVIEW_TYPE_ID); - return { submissionId, submission }; - }); -} - -/** - * @static - * @desc Creates an action that signals beginning of fetching challenge statistics - * @return {Action} - */ -function fetchChallengeStatisticsInit() {} - -/** - * @static - * @desc Creates an action that gets challenge statistics from the backend. - * @param {String} challengeId The challenge id - * @param {String} tokenV3 Topcoder auth token v3. - * @return {Action} - */ -function fetchChallengeStatisticsDone(challengeId, tokenV3) { - const challengeService = getChallengesService(tokenV3); - return challengeService.getChallengeStatistics(challengeId); -} - -export default createActions({ - CHALLENGE: { - DROP_CHECKPOINTS: dropCheckpoints, - DROP_RESULTS: dropResults, - FETCH_CHECKPOINTS_INIT: fetchCheckpointsInit, - FETCH_CHECKPOINTS_DONE: fetchCheckpointsDone, - GET_DETAILS_INIT: getDetailsInit, - GET_DETAILS_DONE: getDetailsDone, - GET_SUBMISSIONS_INIT: getSubmissionsInit, - GET_SUBMISSIONS_DONE: getSubmissionsDone, - LOAD_RESULTS_INIT: loadResultsInit, - LOAD_RESULTS_DONE: loadResultsDone, - REGISTER_INIT: registerInit, - REGISTER_DONE: registerDone, - TOGGLE_CHECKPOINT_FEEDBACK: toggleCheckpointFeedback, - UNREGISTER_INIT: unregisterInit, - UNREGISTER_DONE: unregisterDone, - UPDATE_CHALLENGE_INIT: updateChallengeInit, - UPDATE_CHALLENGE_DONE: updateChallengeDone, - GET_ACTIVE_CHALLENGES_COUNT_INIT: getActiveChallengesCountInit, - GET_ACTIVE_CHALLENGES_COUNT_DONE: getActiveChallengesCountDone, - GET_MM_SUBMISSIONS_INIT: getMMSubmissionsInit, - GET_MM_SUBMISSIONS_DONE: getMMSubmissionsDone, - GET_SUBMISSION_INFORMATION_INIT: getSubmissionInformationInit, - GET_SUBMISSION_INFORMATION_DONE: getSubmissionInformationDone, - FETCH_CHALLENGE_STATISTICS_INIT: fetchChallengeStatisticsInit, - FETCH_CHALLENGE_STATISTICS_DONE: fetchChallengeStatisticsDone, - }, -}); diff --git a/src/apps/earn/src/actions/challenges.js b/src/apps/earn/src/actions/challenges.js deleted file mode 100644 index 780839e74..000000000 --- a/src/apps/earn/src/actions/challenges.js +++ /dev/null @@ -1,140 +0,0 @@ -import { createActions } from "redux-actions"; -import _ from "lodash"; -import service from "../services/challenges"; -import * as util from "../utils/challenge"; -import * as constants from "../constants"; -import { decodeToken } from "tc-auth-lib"; -import { getAuthUserTokens } from "../utils/auth"; - -async function doGetChallenges(filter, cancellationSignal) { - return service.getChallenges(filter, cancellationSignal); -} - -async function getAllActiveChallenges(filter, signal) { - const allActiveFilter = { - ...util.createChallengeCriteria(filter), - ...util.createAllActiveChallengeCriteria(), - }; - return doGetChallenges(allActiveFilter, signal); -} - -async function getOpenForRegistrationChallenges(filter, signal) { - const openForRegistrationFilter = { - ...util.createChallengeCriteria(filter), - ...util.createOpenForRegistrationChallengeCriteria(), - }; - return doGetChallenges(openForRegistrationFilter, signal); -} - -async function getClosedChallenges(filter, signal) { - const closedFilter = { - ...util.createChallengeCriteria(filter), - ...util.createClosedChallengeCriteria(), - }; - return doGetChallenges(closedFilter, signal); -} - -async function getMyChallenges(filter, signal) { - const { tokenV3 } = await getAuthUserTokens(); - const user = decodeToken(tokenV3); - - const myFilter = { - ...util.createChallengeCriteria(filter), - ...util.createMyChallengeCriteria(user.userId), - }; - return doGetChallenges(myFilter, signal); -} - -async function getReviewOpportunities(filter, signal) { - const reviewOpportunities = await service.getReviewOpportunities(signal); - - //Convert from the reviewOpportunity into a challenge object for display in the UI - const challenges = reviewOpportunities.result.content.map((item) => { - let challenge={}; - challenge.id = item.id; - challenge.name = item.challenge.title; - challenge.legacy = {}; - challenge.legacy.id = item.challenge.id; - challenge.track = item.challenge.track; - challenge.overview = {}; - challenge.overview.totalPrizes = item.payments[0].payment; - challenge.reviewPayment = item.payments[0].payment; - challenge.type = "Contest Review"; - challenge.tags = item.challenge.technologies; - challenge.openPositions = item.openPositions; - challenge.numOfSubmissions = item.submissions; - - return challenge; - }) - return challenges; -} - -async function getOpenForRegistrationCount(filter, signal) { - const openForRegistrationCountCriteria = { - ...util.createChallengeCriteria(filter), - ...util.createOpenForRegistrationCountCriteria(), - }; - return doGetChallenges(openForRegistrationCountCriteria, signal); -} - -async function getChallenges(filter, signal) { - const ALL_ACTIVE_CHALLENGES_BUCKET = constants.FILTER_BUCKETS[0]; - const OPEN_FOR_REGISTRATION_BUCKET = constants.FILTER_BUCKETS[1]; - const CLOSED_CHALLENGES = constants.FILTER_BUCKETS[2]; - const MY_CHALLENGES = constants.FILTER_BUCKETS[3]; - const REVIEW_OPPORTUNITIES = constants.FILTER_BUCKETS[4]; - - let challenges; - let total; - let openForRegistrationCount; - - const getChallengesByBucket = async (f) => { - const promises = []; - switch (f.bucket) { - case ALL_ACTIVE_CHALLENGES_BUCKET: - promises.push(getAllActiveChallenges(f, signal)); - break; - case OPEN_FOR_REGISTRATION_BUCKET: - promises.push(getOpenForRegistrationChallenges(f, signal)); - break; - case CLOSED_CHALLENGES: - promises.push(getClosedChallenges(f, signal)); - break; - case MY_CHALLENGES: - promises.push(getMyChallenges(f, signal)); - break; - case REVIEW_OPPORTUNITIES: - promises.push(getReviewOpportunities(f, signal)); - break; - default: - return [util.createEmptyResult(), 0]; - } - promises.push(getOpenForRegistrationCount(f, signal)); - return Promise.all(promises).then((result) => [ - result[0], - result[1].meta.total, - ]); - }; - - if (!util.checkRequiredFilterAttributes(filter)) { - return { - challenges: [], - total: 0, - openForRegistrationCount: 0, - }; - } - - [challenges, openForRegistrationCount] = await getChallengesByBucket(filter); - if(challenges.meta) - total = challenges.meta.total; - else - total = 0 - - return { challenges, total, openForRegistrationCount }; -} - -export default createActions({ - GET_CHALLENGES_INIT: _.noop(), - GET_CHALLENGES_DONE: getChallenges, - GET_CHALLENGES_FAILURE: _.noop, -}); diff --git a/src/apps/earn/src/actions/errors.js b/src/apps/earn/src/actions/errors.js deleted file mode 100644 index f42857512..000000000 --- a/src/apps/earn/src/actions/errors.js +++ /dev/null @@ -1,22 +0,0 @@ -/** - * @module "actions.errors" - * @desc Actions related to the standard application-wide error handling and - * messaging. - * - * Pending to be documented. You are not supposed to use them directly anyway. - * - * @todo This module does not belong to `topcoder-react-lib`, it will be moved - * to `topcoder-react-utils` soon. - */ -import _ from "lodash"; -import { createActions } from "redux-actions"; - -export default createActions({ - ERRORS: { - CLEAR_ERROR: _.noop, - NEW_ERROR: (title, details) => ({ title, details }), - CLEAR_ALL_ERROR_ICONS: _.noop, - SET_ERROR_ICON: (id, title, message) => ({ id, title, message }), - CLEAR_ERROR_ICON: (id) => ({ id }), - }, -}); diff --git a/src/apps/earn/src/actions/filter.js b/src/apps/earn/src/actions/filter.js deleted file mode 100644 index 442e2be58..000000000 --- a/src/apps/earn/src/actions/filter.js +++ /dev/null @@ -1,34 +0,0 @@ -import { createActions } from "redux-actions"; -import * as utils from "../utils"; - -function updateFilter(partialUpdate) { - return partialUpdate; -} - -function clearChallengeFilter(defaultFilter) { - return defaultFilter; -} - -function updateChallengeQuery(filter) { - const params = utils.challenge.createChallengeParams(filter); - utils.url.updateQuery(params); - return params; -} - -function updateGigFilter(partialUpdate) { - return partialUpdate; -} - -function updateGigQuery(filter) { - const params = utils.myGig.createGigParams(filter); - utils.url.updateQuery(params); - return params; -} - -export default createActions({ - UPDATE_FILTER: updateFilter, - CLEAR_CHALLENGE_FILTER: clearChallengeFilter, - UPDATE_CHALLENGE_QUERY: updateChallengeQuery, - UPDATE_GIG_FILTER: updateGigFilter, - UPDATE_GIG_QUERY: updateGigQuery, -}); diff --git a/src/apps/earn/src/actions/gig-apply/creators.js b/src/apps/earn/src/actions/gig-apply/creators.js deleted file mode 100644 index d15e1b4a9..000000000 --- a/src/apps/earn/src/actions/gig-apply/creators.js +++ /dev/null @@ -1,42 +0,0 @@ -import { createActions } from "redux-actions"; -import * as ACTION_TYPE from "./types"; - -const actions = createActions( - {}, - ACTION_TYPE.INIT_PROFILE_DATA, - ACTION_TYPE.RESET_APPLICATION, - ACTION_TYPE.RESET_FORM, - ACTION_TYPE.SEND_APPLICATION_ERROR, - ACTION_TYPE.SEND_APPLICATION_PENDING, - ACTION_TYPE.SEND_APPLICATION_SUCCESS, - ACTION_TYPE.SET_AGREED_DURATION, - ACTION_TYPE.SET_AGREED_TERMS, - ACTION_TYPE.SET_AGREED_TIMEZONE, - ACTION_TYPE.SET_CITY, - ACTION_TYPE.SET_COUNTRY, - ACTION_TYPE.SET_PAYMENT, - ACTION_TYPE.SET_PHONE, - ACTION_TYPE.SET_REFERRAL, - ACTION_TYPE.SET_RESUME, - ACTION_TYPE.SET_SKILLS, - ACTION_TYPE.TOUCH_AGREED_DURATION, - ACTION_TYPE.TOUCH_AGREED_TERMS, - ACTION_TYPE.TOUCH_AGREED_TIMEZONE, - ACTION_TYPE.TOUCH_CITY, - ACTION_TYPE.TOUCH_PAYMENT, - ACTION_TYPE.TOUCH_PHONE, - ACTION_TYPE.TOUCH_RESUME, - ACTION_TYPE.TOUCH_SKILLS, - ACTION_TYPE.TOUCH_REFERRAL, - ACTION_TYPE.VALIDATE_CITY, - ACTION_TYPE.VALIDATE_COUNTRY, - ACTION_TYPE.VALIDATE_PAYMENT, - ACTION_TYPE.VALIDATE_PHONE, - ACTION_TYPE.VALIDATE_RESUME, - ACTION_TYPE.VALIDATE_SKILLS, - ACTION_TYPE.VALIDATE_REFERRAL, - ACTION_TYPE.VALIDATE_UNTOUCHED, - { prefix: "GIG-APPLY", namespace: "--" } -); - -export default actions; diff --git a/src/apps/earn/src/actions/gig-apply/effectors.js b/src/apps/earn/src/actions/gig-apply/effectors.js deleted file mode 100644 index 4e970bd1e..000000000 --- a/src/apps/earn/src/actions/gig-apply/effectors.js +++ /dev/null @@ -1,73 +0,0 @@ -import store from "../../store"; - -import * as applySelectors from "../../reducers/gig-apply/selectors"; -import * as detailsSelectors from "../../reducers/gig-details/selectors"; -import * as gigsSelectors from "../../reducers/gigs/selectors"; -import * as lookupSelectors from "../../reducers/lookupSelectors"; -import * as myGigsSelectors from "../../reducers/my-gigs/selectors"; -import * as detailsEffectors from "../../actions/gig-details/effectors"; -import * as applyServices from "../../services/gig-apply"; -import { composeApplication } from "../../utils/gig-apply"; -import lookupActions from "../lookup"; -import applyActions from "./creators"; - -/** - * Loads gig details and countries. Must be called after the user's profile - * has loaded. - */ -export const loadInitialData = async (externalId) => { - const { dispatch, getState } = store; - const promises = []; - let details = detailsSelectors.getDetails(getState()); - if (!details) { - promises.push(detailsEffectors.loadDetails(store, externalId)); - } - let countryByCode = lookupSelectors.getCountryByCode(getState()); - if (!countryByCode) { - promises.push(dispatch(lookupActions.lookup.getAllCountries())); - } - try { - await Promise.all(promises); - } catch (error) { - console.error(error); - } - const state = getState(); - const profile = myGigsSelectors.getProfile(state); - countryByCode = lookupSelectors.getCountryByCode(state); - const country = countryByCode[profile.country]?.name || ""; - const skillsByName = gigsSelectors.getSkillsByName(state); - const skillNames = profile.skill?.split(",") || []; - let skills = null; - for (let name of skillNames) { - let skill = skillsByName[name]; - if (skill) { - // initialize it with empty array as long as we have at least one skill - if (!skills) skills = []; - skills.push(skill); - } - } - dispatch(applyActions.initProfileData({ country, profile, skills })); -}; - -export const sendApplication = async () => { - const { dispatch, getState } = store; - const state = getState(); - dispatch(applyActions.validateUntouched()); - const isFormValid = applySelectors.getIsFormValid(state); - - if (!isFormValid) { - // stop the application by return directly - return; - } - const { jobExternalId } = detailsSelectors.getDetails(state); - const formData = composeApplication(state); - dispatch(applyActions.sendApplicationPending()); - let data = null; - try { - data = await applyServices.sendApplication(jobExternalId, formData); - } catch (error) { - dispatch(applyActions.sendApplicationError(error.toString())); - return; - } - dispatch(applyActions.sendApplicationSuccess(data)); -}; diff --git a/src/apps/earn/src/actions/gig-apply/types.js b/src/apps/earn/src/actions/gig-apply/types.js deleted file mode 100644 index fae9997b4..000000000 --- a/src/apps/earn/src/actions/gig-apply/types.js +++ /dev/null @@ -1,33 +0,0 @@ -export const INIT_PROFILE_DATA = "INIT_PROFILE_DATA"; -export const RESET_APPLICATION = "RESET_APPLICATION"; -export const RESET_FORM = "RESET_FORM"; -export const SEND_APPLICATION_ERROR = "SEND_APPLICATION_ERROR"; -export const SEND_APPLICATION_PENDING = "SEND_APPLICATION_PENDING"; -export const SEND_APPLICATION_SUCCESS = "SEND_APPLICATION_SUCCESS"; -export const SET_AGREED_DURATION = "SET_AGREED_DURATION"; -export const SET_AGREED_TERMS = "SET_AGREED_TERMS"; -export const SET_AGREED_TIMEZONE = "SET_AGREED_TIMEZONE"; -export const SET_CITY = "SET_CITY"; -export const SET_COUNTRY = "SET_COUNTRY"; -export const SET_PAYMENT = "SET_PAYMENT"; -export const SET_PHONE = "SET_PHONE"; -export const SET_REFERRAL = "SET_REFERRAL"; -export const SET_RESUME = "SET_RESUME"; -export const SET_SKILLS = "SET_SKILLS"; -export const TOUCH_AGREED_DURATION = "TOUCH_AGREED_DURATION"; -export const TOUCH_AGREED_TERMS = "TOUCH_AGREED_TERMS"; -export const TOUCH_AGREED_TIMEZONE = "TOUCH_AGREED_TIMEZONE"; -export const TOUCH_CITY = "TOUCH_CITY"; -export const TOUCH_PAYMENT = "TOUCH_PAYMENT"; -export const TOUCH_PHONE = "TOUCH_PHONE"; -export const TOUCH_RESUME = "TOUCH_RESUME"; -export const TOUCH_SKILLS = "TOUCH_SKILLS"; -export const TOUCH_REFERRAL = "TOUCH_REFERRAL"; -export const VALIDATE_CITY = "VALIDATE_CITY"; -export const VALIDATE_COUNTRY = "VALIDATE_COUNTRY"; -export const VALIDATE_PAYMENT = "VALIDATE_PAYMENT"; -export const VALIDATE_PHONE = "VALIDATE_PHONE"; -export const VALIDATE_RESUME = "VALIDATE_RESUME"; -export const VALIDATE_SKILLS = "VALIDATE_SKILLS"; -export const VALIDATE_REFERRAL = "VALIDATE_REFERRAL"; -export const VALIDATE_UNTOUCHED = "VALIDATE_UNTOUCHED"; diff --git a/src/apps/earn/src/actions/gig-details/creators.js b/src/apps/earn/src/actions/gig-details/creators.js deleted file mode 100644 index 8d2a96077..000000000 --- a/src/apps/earn/src/actions/gig-details/creators.js +++ /dev/null @@ -1,14 +0,0 @@ -import { createActions } from "redux-actions"; -import * as ACTION_TYPE from "./types"; - -const actions = createActions( - {}, - ACTION_TYPE.LOAD_DETAILS_ERROR, - ACTION_TYPE.LOAD_DETAILS_PENDING, - ACTION_TYPE.LOAD_DETAILS_SUCCESS, - ACTION_TYPE.RESET_DETAILS, - ACTION_TYPE.SET_CANCEL_RESET, - { prefix: "GIG-DETAILS", namespace: "--" } -); - -export default actions; diff --git a/src/apps/earn/src/actions/gig-details/effectors.js b/src/apps/earn/src/actions/gig-details/effectors.js deleted file mode 100644 index e3c3767b0..000000000 --- a/src/apps/earn/src/actions/gig-details/effectors.js +++ /dev/null @@ -1,54 +0,0 @@ -import { tokenGetAsync } from "~/libs/core"; - -import * as gigsSelectors from "../../reducers/gigs/selectors"; -import * as selectors from "../../reducers/gig-details/selectors"; -import * as services from "../../services/gig-details"; -import { loadSkills } from "../gigs/effectors"; -import { isAbort } from "../../utils/fetch"; - -import actions from "./creators"; - -export const loadDetails = async (store, externalId) => { - const { dispatch, getState } = store; - const skillsPromise = loadSkills(store); - const { token: tokenV3 } = await tokenGetAsync(); - - const [promise, controller] = services.fetchGig(externalId, tokenV3); - dispatch(actions.loadDetailsPending(controller)); - let details = null; - try { - details = await promise; - } catch (error) { - if (!isAbort(error)) { - dispatch(actions.loadDetailsError(error.toString())); - } - return; - } - try { - await skillsPromise; - } catch (error) { - // This should never be reachable but just in case. - console.error(error); - } - const skillsById = gigsSelectors.getSkillsById(getState()); - if (details.skills?.length && skillsById) { - const skills = []; - for (let id of details.skills) { - let skill = skillsById[id]; - if (skill) { - skills.push(skill); - } - } - details.skills = skills; - } - dispatch(actions.loadDetailsSuccess(details)); -}; - -export const resetDetails = ({ dispatch, getState }) => { - const { abortController, cancelReset } = selectors.getStateSlice(getState()); - if (cancelReset) { - return; - } - abortController?.abort(); - dispatch(actions.resetDetails()); -}; diff --git a/src/apps/earn/src/actions/gig-details/types.js b/src/apps/earn/src/actions/gig-details/types.js deleted file mode 100644 index 1cba85ae2..000000000 --- a/src/apps/earn/src/actions/gig-details/types.js +++ /dev/null @@ -1,5 +0,0 @@ -export const LOAD_DETAILS_ERROR = "LOAD_DETAILS_ERROR"; -export const LOAD_DETAILS_PENDING = "LOAD_DETAILS_PENDING"; -export const LOAD_DETAILS_SUCCESS = "LOAD_DETAILS_SUCCESS"; -export const RESET_DETAILS = "RESET_DETAILS"; -export const SET_CANCEL_RESET = "SET_CANCEL_RESET"; diff --git a/src/apps/earn/src/actions/gigs/creators.js b/src/apps/earn/src/actions/gigs/creators.js deleted file mode 100644 index e48071da6..000000000 --- a/src/apps/earn/src/actions/gigs/creators.js +++ /dev/null @@ -1,31 +0,0 @@ -import { createActions } from "redux-actions"; -import * as ACTION_TYPE from "./types"; - -const actions = createActions( - {}, - ACTION_TYPE.ADD_SKILL, - ACTION_TYPE.LOAD_GIGS_SPECIAL_ERROR, - ACTION_TYPE.LOAD_GIGS_SPECIAL_SUCCESS, - ACTION_TYPE.LOAD_PAGE_SUCCESS, - ACTION_TYPE.LOAD_PAGE_ERROR, - ACTION_TYPE.LOAD_PAGE_PENDING, - ACTION_TYPE.LOAD_SKILLS_ERROR, - ACTION_TYPE.LOAD_SKILLS_SUCCESS, - ACTION_TYPE.REMOVE_SKILL, - ACTION_TYPE.RESET_FILTERS, - ACTION_TYPE.SET_LOCATION, - ACTION_TYPE.SET_PAGE_NUMBER, - ACTION_TYPE.SET_PAGE_SIZE, - ACTION_TYPE.SET_PAYMENT_MAX, - ACTION_TYPE.SET_PAYMENT_MAX_VALUE, - ACTION_TYPE.SET_PAYMENT_MIN, - ACTION_TYPE.SET_PAYMENT_MIN_VALUE, - ACTION_TYPE.SET_SKILLS, - ACTION_TYPE.SET_SORTING, - ACTION_TYPE.SET_TITLE, - ACTION_TYPE.UPDATE_STATE_FROM_QUERY, - ACTION_TYPE.UPDATE_FILTERED_SPECIAL_GIGS, - { prefix: "GIGS", namespace: "--" } -); - -export default actions; diff --git a/src/apps/earn/src/actions/gigs/effectors.js b/src/apps/earn/src/actions/gigs/effectors.js deleted file mode 100644 index d30cb95c7..000000000 --- a/src/apps/earn/src/actions/gigs/effectors.js +++ /dev/null @@ -1,163 +0,0 @@ -import actions from "./creators"; -import * as selectors from "../../reducers/gigs/selectors"; -import * as services from "../../services/gigs"; -import lookupServices from "../../services/lookup"; -import { isAbort } from "../../utils/fetch"; -import { makeQueryFromState } from "../../reducers/gigs/urlQuery"; -import { sortByName } from "../../utils/misc"; - -/** - * Loads the specified gigs' page. If page number is not provided the current - * page number from current state is used. All relevant gigs' filters are loaded - * from the current state to construct the request query. - * - * @returns {Promise} - */ -export const loadGigsPage = async ({ dispatch, getState }) => { - const gigsState = selectors.getStateSlice(getState()); - // If there's an ongoing request we just cancel it since the data that comes - // with its response will not correspond to application's current state, - // namely filters and sorting. - gigsState.abortController?.abort(); - const { filters, sorting, pagination } = gigsState; - const { location, paymentMax, paymentMin, skills, title } = filters; - const { pageNumber, pageSize } = pagination; - const { sortBy, sortOrder } = sorting; - const [promise, abortController] = services.fetchGigs({ - location, - pageNumber, - pageSize, - paymentMax, - paymentMin, - skills, - sortBy, - sortOrder, - title, - featured: false, - }); - dispatch(actions.loadPagePending(abortController)); - let gigs, pageCount, totalCount; - try { - const { data, pagination } = await promise; - gigs = data; - pageCount = pagination.pageCount; - totalCount = pagination.totalCount; - } catch (error) { - // If request was cancelled by the next call to loadGigsPage - // there's nothing more to do. - if (!isAbort(error)) { - dispatch(actions.loadPageError(error.message)); - } - return; - } - dispatch(actions.loadPageSuccess({ gigs, pageCount, totalCount })); - dispatch(actions.updateFilteredSpecialGigs()); -}; - -/** - * Loads promo (hotlist) gigs. - * - * @param {Object} store redux store object - * @returns {Promise} - */ -export const loadGigsSpecial = async ({ dispatch }) => { - let gigsSpecial = null; - const [promise] = services.fetchGigs({ - pageNumber: 1, - special: true, - }); - try { - let { data } = await promise; - gigsSpecial = data; - } catch (error) { - dispatch(actions.loadGigsSpecialError(error.toString())); - console.error(error); - return; - } - dispatch(actions.loadGigsSpecialSuccess(gigsSpecial)); -}; - -/** - * Loads skills and special (featured + hotlist) gigs. - * - * @param {Object} store redux store object - */ -export const loadInitialData = async (store) => { - const { dispatch } = store; - let skillsPromise = loadSkills(store); - let gigsSpecial = null; - const [promise] = services.fetchGigs({ - pageNumber: 1, - special: true, - }); - try { - let { data } = await promise; - gigsSpecial = data; - } catch (error) { - dispatch(actions.loadGigsSpecialError(error.toString())); - console.error(error); - return; - } - // skills must be present in the store before we can process special gigs - await skillsPromise; - dispatch(actions.loadGigsSpecialSuccess(gigsSpecial)); -}; - -/** - * Loads all gigs' skills. - * - * @param {Object} store redux store - * @returns {Promise} - */ -export const loadSkills = async ({ dispatch, getState }) => { - const hasSkills = selectors.getHasSkills(getState()); - if (hasSkills) { - return; - } - const pageSize = 1e3; - let skills = null; - try { - const response = await lookupServices.getPaginatedSkills(1, pageSize); - const totalPages = +response.meta?.totalPages || 0; - const promises = [Promise.resolve(response)]; - for (let p = 2; p <= totalPages; p++) { - promises.push(lookupServices.getPaginatedSkills(p, pageSize)); - } - skills = (await Promise.all(promises)).flat().sort(sortByName); - } catch (error) { - dispatch(actions.loadSkillsError(error.toString())); - console.error(error); - return; - } - dispatch(actions.loadSkillsSuccess(skills)); -}; - -/** - * Updates state from current query (which may be empty) and then updates - * URL query by replacing URL in history. - * - * @param {Object} store redux store object - * @param {Object} options.mountLocation location object - */ -export const updateStateAndQuery = ({ dispatch, getState }, options) => { - const location = options ? options.mountLocation : window.location; - const isGigsLocation = location.pathname === window.location.pathname; - - dispatch(actions.updateStateFromQuery(location.search)); - const query = makeQueryFromState(selectors.getStateSlice(getState())); - - if (isGigsLocation) { - window.history.replaceState(null, "", `${location.pathname}?${query}`); - } -}; - -/** - * Updates URL query from current state by pushing new URL into history. - * - * @param {Object} store redux store object - */ -export const updateUrlQuery = ({ getState }) => { - const location = window.location; - const query = makeQueryFromState(selectors.getStateSlice(getState())); - window.history.pushState(null, "", `${location.pathname}?${query}`); -}; diff --git a/src/apps/earn/src/actions/gigs/types.js b/src/apps/earn/src/actions/gigs/types.js deleted file mode 100644 index 401ba029b..000000000 --- a/src/apps/earn/src/actions/gigs/types.js +++ /dev/null @@ -1,22 +0,0 @@ -export const ADD_SKILL = "ADD_SKILL"; -export const LOAD_GIGS_SPECIAL_ERROR = "LOAD_GIGS_SPECIAL_ERROR"; -export const LOAD_GIGS_SPECIAL_SUCCESS = "LOAD_GIGS_SPECIAL_SUCCESS"; -export const LOAD_PAGE_ERROR = "LOAD_PAGE_ERROR"; -export const LOAD_PAGE_PENDING = "LOAD_PAGE_PENDING"; -export const LOAD_PAGE_SUCCESS = "LOAD_PAGE_SUCCESS"; -export const LOAD_SKILLS_ERROR = "LOAD_SKILLS_ERROR"; -export const LOAD_SKILLS_SUCCESS = "LOAD_SKILLS_SUCCESS"; -export const REMOVE_SKILL = "REMOVE_SKILL"; -export const RESET_FILTERS = "RESET_FILTERS"; -export const SET_LOCATION = "SET_LOCATION"; -export const SET_PAGE_NUMBER = "SET_PAGE_NUMBER"; -export const SET_PAGE_SIZE = "SET_PAGE_SIZE"; -export const SET_PAYMENT_MAX = "SET_PAYMENT_MAX"; -export const SET_PAYMENT_MAX_VALUE = "SET_PAYMENT_MAX_VALUE"; -export const SET_PAYMENT_MIN = "SET_PAYMENT_MIN"; -export const SET_PAYMENT_MIN_VALUE = "SET_PAYMENT_MIN_VALUE"; -export const SET_SKILLS = "SET_SKILLS"; -export const SET_SORTING = "SET_SORTING"; -export const SET_TITLE = "SET_TITLE"; -export const UPDATE_STATE_FROM_QUERY = "UPDATE_STATE_FROM_QUERY"; -export const UPDATE_FILTERED_SPECIAL_GIGS = "UPDATE_FILTERED_SPECIAL_GIGS"; diff --git a/src/apps/earn/src/actions/index.js b/src/apps/earn/src/actions/index.js deleted file mode 100644 index 4b9e943ef..000000000 --- a/src/apps/earn/src/actions/index.js +++ /dev/null @@ -1,29 +0,0 @@ -import auth from "./auth"; -import challenge from "./challenge.js"; -import challengeListing from "./challenge-listing"; -import challenges from "./challenges"; -import errors from "./errors"; -import filter from "./filter"; -import lookup from "./lookup"; -import myGigs from "./my-gigs"; -import page from "./page/challenge-details"; -import submission from "./submission"; -import submissionManagement from "./submissionManagement"; -import smp from "./smp"; - -export const actions = { - challenges, - filter, - myGigs, - submission, - submissionManagement, - ...smp, - ...challenge, - ...challengeListing, - ...auth, - ...lookup, - ...page, - ...errors, -}; - -export default actions; diff --git a/src/apps/earn/src/actions/lookup.js b/src/apps/earn/src/actions/lookup.js deleted file mode 100644 index e1ba99ff6..000000000 --- a/src/apps/earn/src/actions/lookup.js +++ /dev/null @@ -1,199 +0,0 @@ -/** - * @module "actions.lookup" - * @desc Actions related to lookup data. - */ - import { createActions } from "redux-actions"; - import service, { getService } from "../services/lookup"; - - async function getTags() { - return service.getTags(); - } - - async function getCommunityList() { - return service.getCommunityList(); - } - /* - * device api PAGE_SIZE - */ - export const PAGE_SIZE = 100; - - /** - * @static - * @desc Creates an action that signals beginning of getting all deveice types - * @return {Action} - */ - function getTypesInit() {} - - /** - * @static - * @desc Creates an action that get all deveice types - * @return {Action} - */ - function getTypesDone() { - return getService().getTypes(); - } - - /** - * @static - * @desc Creates an action that signals beginning of getting all manufacturers - * @return {Action} - */ - function getManufacturersInit() {} - - /** - * @static - * @desc Creates an action that get all deveice manufacturers - * @param {String} type - * @return {Action} - */ - function getManufacturersDone(type) { - return getService().getManufacturers(type); - } - - /** - * @static - * @desc Creates an action that signals beginning of getting models - * @param {Number} page - * @return {Action} - */ - function getModelsInit(page) { - return { - page, - }; - } - - /** - * @static - * @desc Creates an action that get all deveice models - * @param {Number} page - * @param {String} manufacturer - * @param {String} type - * @return {Action} - */ - function getModelsDone(page, type, manufacturer) { - return getService().getDevices(page, PAGE_SIZE, type, manufacturer); - } - - /** - * @static - * @desc Creates an action that signals beginning of getting operation systems - * - * @param {Number} page - * @return {Action} - */ - function getOsesInit(page) { - return { - page, - }; - } - - /** - * @static - * @desc Creates an action that get all operation systems - * @param {Number} page - * @param {String} manufacturer - * @param {String} type - * @param {String} model - * @return {Action} - */ - function getOsesDone(page, type, manufacturer, model) { - return getService().getDevices(page, PAGE_SIZE, type, manufacturer, model); - } - - /** - * @static - * @desc Creates an action that signals beginning of getting all skill tags. - * @return {Action} - */ - function getSkillTagsInit() {} - - /** - * @static - * @desc Creates an action that gets all skill tags. - * @return {Action} - */ - function getSkillTagsDone() { - const params = { - filter: { - domain: "SKILLS", - status: "APPROVED", - }, - limit: { - limit: 1000, - }, - }; - return getService().getTags(params); - } - - /** - * @static - * @desc Creates an action that signals beginning of getting all countries. - * @return {Action} - */ - function getCountriesInit() {} - - /** - * @static - * @desc Creates an action that gets all countries. - * @return {Action} - */ - function getCountriesDone() { - return getService().getCountries(); - } - - /** - * @static - * @desc Creates an action that signals beginning of getting all review types. - * @return {Action} - */ - function getReviewTypesInit() {} - - /** - * @static - * @desc Creates an action that gets all review types. - * @param {String} tokenV3 Optional. Auth token for Topcoder API v3. - * @return {Action} - */ - function getReviewTypesDone(tokenV3) { - return getService(tokenV3).getReviewTypes(); - } - - /** - * @static - * @desc Creates an action that signals beginning of getting all countries api version 5. - * @return {Action} - */ - function getAllCountriesInit() {} - - /** - * @static - * @desc Creates an action that gets all countries api version 5. - * @param {String} tokenV3 Optional. Auth token for Topcoder API v3. - * @return {Action} - */ - function getAllCountries(tokenV3) { - return getService(tokenV3).getAllCountries(); - } - - export default createActions({ - LOOKUP: { - GET_TAGS_DONE: getTags, - GET_COMMUNITY_LIST_DONE: getCommunityList, - GET_TYPES_INIT: getTypesInit, - GET_TYPES_DONE: getTypesDone, - GET_MANUFACTURERS_INIT: getManufacturersInit, - GET_MANUFACTURERS_DONE: getManufacturersDone, - GET_MODELS_INIT: getModelsInit, - GET_MODELS_DONE: getModelsDone, - GET_OSES_INIT: getOsesInit, - GET_OSES_DONE: getOsesDone, - GET_SKILL_TAGS_INIT: getSkillTagsInit, - GET_SKILL_TAGS_DONE: getSkillTagsDone, - GET_COUNTRIES_INIT: getCountriesInit, - GET_COUNTRIES_DONE: getCountriesDone, - GET_REVIEW_TYPES_INIT: getReviewTypesInit, - GET_REVIEW_TYPES_DONE: getReviewTypesDone, - GET_ALL_COUNTRIES_INIT: getAllCountriesInit, - GET_ALL_COUNTRIES: getAllCountries, - }, - }); diff --git a/src/apps/earn/src/actions/my-gigs.js b/src/apps/earn/src/actions/my-gigs.js deleted file mode 100644 index 661cfd527..000000000 --- a/src/apps/earn/src/actions/my-gigs.js +++ /dev/null @@ -1,78 +0,0 @@ -import { createActions } from "redux-actions"; -import _ from "lodash"; -import { - PER_PAGE, - CHECKING_GIG_TIMES, - DELAY_CHECK_GIG_TIME, -} from "../constants"; -import service from "../services/my-gigs"; - -async function getMyActiveGigs( - status = "active_jobs", - page = 1, - perPage = PER_PAGE -) { - return service.getMyGigs(status, page, perPage); -} - -async function getMyOpenGigs( - status = "open_jobs", - page = 1, - perPage = PER_PAGE -) { - return service.getMyGigs(status, page, perPage); -} - -async function getMyCompletedGigs( - status = "completed_jobs", - page = 1, - perPage = PER_PAGE -) { - return service.getMyGigs(status, page, perPage); -} - -async function getMyArchivedGigsDone( - status = "archived_jobs", - page = 1, - perPage = PER_PAGE -) { - return service.getMyGigs(status, page, perPage); -} - -async function getProfile() { - return service.getProfile(); -} - -async function updateProfile(profile) { - return service.updateProfile(profile); -} - -async function startCheckingGigs(externalId) { - let i = 0; - while (i < CHECKING_GIG_TIMES) { - const res = await service.startCheckingGigs(externalId); - if (res && !res.synced) { - await new Promise((resolve) => { - setTimeout(() => { - resolve(); - }, DELAY_CHECK_GIG_TIME); - }); - i++; - continue; - } else { - return {}; - } - } - return {}; -} - -export default createActions({ - GET_MY_ACTIVE_GIGS: getMyActiveGigs, - GET_MY_OPEN_GIGS: getMyOpenGigs, - GET_MY_COMPLETED_GIGS: getMyCompletedGigs, - GET_MY_ARCHIVED_GIGS: getMyArchivedGigsDone, - GET_PROFILE: getProfile, - UPDATE_PROFILE: updateProfile, - UPDATE_PROFILE_RESET: _.noop, - START_CHECKING_GIGS: startCheckingGigs, -}); diff --git a/src/apps/earn/src/actions/page/challenge-details.js b/src/apps/earn/src/actions/page/challenge-details.js deleted file mode 100644 index 3bf4ce082..000000000 --- a/src/apps/earn/src/actions/page/challenge-details.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Actions related to the UI state of challenge details page. - */ - import _ from 'lodash'; - import { createActions } from 'redux-actions'; - - /** - * String values of valid tab names. - */ - export const TABS = { - DETAILS: 'details', - REGISTRANTS: 'registrants', - CHECKPOINTS: 'checkpoints', - SUBMISSIONS: 'submissions', - MY_SUBMISSIONS: 'my submissions', - WINNERS: 'winners', - CHALLENGE_FORUM: 'challenge_forum', - MM_DASHBOARD: 'dashboard', - }; - - /* Holds valid values for the specs tab state. */ - export const SPECS_TAB_STATES = { - EDIT: 'EDIT', - VIEW: 'VIEW', - SAVING: 'SAVING', - }; - - /** - * Creates action that switches the page to the specified content tab. - * @param {String} tab One of `TAB` values. - * @return {Action} - */ - function selectTab(tab) { - return tab; - } - - /** - * Creates action that toggle the submission history. - * @param {Number} index of subbmission history. - * @return {Action} - */ - function toggleSubmissionHistory(index) { - return index; - } - - /** - * Sets the state of specs tab. - * @param {String} state One of SPECS_TAB_STATES keys. - * @return {String} - */ - function setSpecsTabState(state) { - return state; - } - - /** - * Toggles checkpoint feedback. If second argument is provided, it - * will just open / close the checkpoint depending on its value being - * true or false. - * @param {Number} id - * @param {Boolean} open - * @return {Object} - */ - function toggleCheckpointFeedback(id, open = false) { - return { id, open }; - } - - /** - * Creates action that toggle the submission testcase.. - * @param {Number} index of submission testcase. - * @return {Action} - */ - function toggleSubmissionTestCase(index) { - return index; - } - - export default createActions({ - PAGE: { - CHALLENGE_DETAILS: { - SELECT_TAB: selectTab, - SET_SPECS_TAB_STATE: setSpecsTabState, - TOGGLE_CHECKPOINT_FEEDBACK: toggleCheckpointFeedback, - SUBMISSIONS: { - TOGGLE_SUBMISSION_HISTORY: toggleSubmissionHistory, - TOGGLE_SUBMISSION_TESTCASE: toggleSubmissionTestCase, - CLEAR_SUBMISSION_TESTCASE_OPEN: _.identity, - }, - }, - }, - }); - \ No newline at end of file diff --git a/src/apps/earn/src/actions/page/index.js b/src/apps/earn/src/actions/page/index.js deleted file mode 100644 index f72a18973..000000000 --- a/src/apps/earn/src/actions/page/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import _ from "lodash"; -import challengeDetails from "./challenge-details"; -import submission_management from "./submission_management.js"; - -export default _.merge({}, challengeDetails, submission_management); diff --git a/src/apps/earn/src/actions/page/submission.js b/src/apps/earn/src/actions/page/submission.js deleted file mode 100644 index f2a8f1f68..000000000 --- a/src/apps/earn/src/actions/page/submission.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * actions.page.challenge-details.submission - * - * Description: - * Contains the Redux Actions for updating the Submission page UI - * and for for uploading submissions to back end - */ -import _ from 'lodash'; -import { createActions } from 'redux-actions'; -import config from '@earn/config'; -import { getApi } from "@earn/services/lib/api"; - -/** - * Payload creator for the action that actually performs submission operation. - * @param {String} tokenV3 - * @param {String} tokenV2 - * @param {String} submissionId - * @param {Object} body Data to submit. - * @param {String} track Competition track of the challenge where we submit. - * @param {Function} progress The callback to trigger with updates on the - * submission progress. - * @return Promise - */ -function submitDone(tokenV3, tokenV2, submissionId, body, track, progress) { - const api = getApi('V5', tokenV3); - const url = '/submissions/'; - return api.upload(url, { - body, - method: 'POST', - }, progress).then((res) => { - const jres = JSON.parse(res); - return jres; - }, (err) => { - throw err; - }); -} - -/** - * Export Actions for usage by Redux - */ -export default createActions({ - PAGE: { - SUBMISSION: { - SUBMIT_INIT: _.noop, - SUBMIT_DONE: submitDone, - SUBMIT_RESET: _.noop, - UPLOAD_PROGRESS: percent => percent, - SET_AGREED: agreed => agreed, - SET_FILE_PICKER_ERROR: (id, error) => ({ id, error }), - SET_FILE_PICKER_FILE_NAME: (id, fileName) => ({ id, fileName }), - SET_FILE_PICKER_UPLOAD_PROGRESS: (id, progress) => ({ id, progress }), - SET_FILE_PICKER_DRAGGED: (id, dragged) => ({ id, dragged }), - UPDATE_NOTES_LENGTH: length => length, - SET_SUBMISSION_FILESTACK_DATA: data => data, - }, - }, -}); diff --git a/src/apps/earn/src/actions/page/submission_management.js b/src/apps/earn/src/actions/page/submission_management.js deleted file mode 100644 index 08bc034d3..000000000 --- a/src/apps/earn/src/actions/page/submission_management.js +++ /dev/null @@ -1,12 +0,0 @@ -import _ from 'lodash'; -import { createActions } from 'redux-actions'; - -export default createActions({ - PAGE: { - SUBMISSION_MANAGEMENT: { - SHOW_DETAILS: _.identity, - CANCEL_DELETE: _.noop, - CONFIRM_DELETE: _.identity, - }, - }, -}); diff --git a/src/apps/earn/src/actions/smp.js b/src/apps/earn/src/actions/smp.js deleted file mode 100644 index ff3cded55..000000000 --- a/src/apps/earn/src/actions/smp.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @module "actions.smp" - * @desc Actions related to *My Submissions Management* page. - */ - -import _ from "lodash"; -import { createActions } from "redux-actions"; -import { getApi } from "../services/challenge-api"; - -/** - * @static - * @desc Creates an action that signals beginning of submission download. - * @return {Action} - */ -function deleteSubmissionInit() {} - -/** - * @static - * @desc Creates an action that deletes user's submission to a challenge. - * @param {String} tokenV3 Topcoder v3 auth token. - * @param {Number|String} submissionId Submission ID. - * @return {Action} - */ -function deleteSubmissionDone(tokenV3, submissionId) { - return getApi("V5", tokenV3) - .delete(`/submissions/${submissionId}`) - .then(() => submissionId); -} - -/** - * @static - * @todo At this moment we don't need any special JS code to download - * submissions: we get them from legacy Topcoder Studio API, which is - * authenticated by cookies, and can be done with a simple link in - * the component. Soon we'll migrate to use the new TC API instead, and - * then we'll decide, whether we need operate downloads in JS, or can we - * just remove this action. - * @return {Action} - */ -function downloadSubmission(tokens, type, submissionId) { - _.noop(tokens, type, submissionId); -} - -export default createActions({ - SMP: { - DELETE_SUBMISSION_DONE: deleteSubmissionDone, - DELETE_SUBMISSION_INIT: deleteSubmissionInit, - DOWNLOAD_SUBMISSION: downloadSubmission, - }, -}); diff --git a/src/apps/earn/src/actions/submission.js b/src/apps/earn/src/actions/submission.js deleted file mode 100644 index 9a67f8763..000000000 --- a/src/apps/earn/src/actions/submission.js +++ /dev/null @@ -1,36 +0,0 @@ -/** - * actions.page.challenge-details.submission - * - * Description: - * Contains the Redux Actions for updating the Submission page UI - * and for for uploading submissions to back end - */ -import _ from "lodash"; -import { createActions } from "redux-actions"; -import service from "../services/submission"; - -/** - * Payload creator for the action that actually performs submission operation. - * @param {Object} data Data to submit. - * @param {Function} onProgress The callback to trigger with updates on the - * submission progress. - * @return Promise - */ -function submitDone(data, onProgress) { - return service.submit(data, onProgress); -} - -export default createActions({ - SUBMIT: { - SUBMIT_INIT: _.noop, - SUBMIT_DONE: submitDone, - SUBMIT_RESET: _.noop, - UPLOAD_PROGRESS: (percent) => percent, - SET_AGREED: (agreed) => agreed, - SET_FILE_PICKER_ERROR: (id, error) => ({ id, error }), - SET_FILE_PICKER_FILE_NAME: (id, fileName) => ({ id, fileName }), - SET_FILE_PICKER_UPLOAD_PROGRESS: (id, progress) => ({ id, progress }), - SET_FILE_PICKER_DRAGGED: (id, dragged) => ({ id, dragged }), - SET_SUBMISSION_FILESTACK_DATA: (data) => data, - }, -}); diff --git a/src/apps/earn/src/actions/submissionManagement.js b/src/apps/earn/src/actions/submissionManagement.js deleted file mode 100644 index 3d541f142..000000000 --- a/src/apps/earn/src/actions/submissionManagement.js +++ /dev/null @@ -1,42 +0,0 @@ -import _ from "lodash"; -import { createActions } from "redux-actions"; -import service from "../services/submission"; -import { decodeToken } from "tc-auth-lib"; -import { triggerDownload } from "../utils"; -import { getAuthUserTokens } from "../utils/auth"; - -function deleteSubmissionDone(submissionId) { - return service.deleteSubmission(submissionId); -} - -async function downloadSubmissionDone(track, submissionId) { - const blob = await service.downloadSubmission(track, submissionId); - triggerDownload( - `submission-${track.toLowerCase()}-${submissionId}.zip`, - blob - ); -} - -async function getMySubmissionsDone(challengeId) { - const { tokenV3 } = await getAuthUserTokens(); - const user = decodeToken(tokenV3); - const filters = { - challengeId, - memberId: user.userId, - }; - - return service.getSubmissions(filters); -} - -export default createActions({ - MY_SUBMISSIONS: { - SHOW_DETAILS: _.identity, - CANCEL_DELETE: _.noop, - CONFIRM_DELETE: _.identity, - DELETE_SUBMISSION_INIT: _.noop, - DELETE_SUBMISSION_DONE: deleteSubmissionDone, - DOWNLOAD_SUBMISSION_DONE: downloadSubmissionDone, - GET_MY_SUBMISSIONS_INIT: _.noop, - GET_MY_SUBMISSIONS_DONE: getMySubmissionsDone, - }, -}); diff --git a/src/apps/earn/src/actions/submission_management.js b/src/apps/earn/src/actions/submission_management.js deleted file mode 100644 index f114d67f1..000000000 --- a/src/apps/earn/src/actions/submission_management.js +++ /dev/null @@ -1,10 +0,0 @@ -import _ from 'lodash'; -import { createActions } from 'redux-actions'; - -export default createActions({ - SUBMISSION_MANAGEMENT: { - SHOW_DETAILS: _.identity, - CANCEL_DELETE: _.noop, - CONFIRM_DELETE: _.identity, - }, -}); diff --git a/src/apps/earn/src/actions/terms.js b/src/apps/earn/src/actions/terms.js deleted file mode 100644 index 181276db3..000000000 --- a/src/apps/earn/src/actions/terms.js +++ /dev/null @@ -1,251 +0,0 @@ -/** - * Terms specific actions. - */ - -import _ from "lodash"; -import { createActions } from "redux-actions"; -import { getService } from "../services/terms"; -import config from "../config"; - -/** - * Payload creator for TERMS/GET_TERMS_DONE action, - * which fetch terms of the specified entity. - * @param {Object} entity entity object - * @param {String} entity.type entity type ['challenge'||'community'] - * @param {String} entity.id entity id - * @param {Object} tokens object with tokenV2 and tokenV3 properties - * @param {Boolean} mockAgreed if true, then all terms will be mocked as agreed - * this only makes effect if MOCK_TERMS_SERVICE is true - * and the only purpose of this param is testing terms - * @return {Promise} - */ -function getTermsDone(entity, tokens, mockAgreed) { - const service = getService(tokens.tokenV3); - let termsPromise; - - // if mockAgreed=true passed, then we create an array of 10 true which we pass to the - // terms service methods. - // when terms service is mocked by setting MOCK_TERMS_SERVICE=true - // it will make all terms to have agreed status (actually only first 10 will be agreed, - // but we will hardly have even more then 3 terms per entity) - const mockAgreedArray = mockAgreed - ? Array(10 + 1) - .join("1") - .split("") - .map(() => true) - : []; - - switch (entity.type) { - case "challenge": { - termsPromise = service.getChallengeTerms(entity.terms, mockAgreedArray); - break; - } - case "community": { - termsPromise = service.getCommunityTerms( - entity.id, - tokens.tokenV3, - mockAgreedArray - ); - break; - } - case "reviewOpportunity": { - termsPromise = service.getReviewOpportunityTerms( - entity.reviewOpportunityTerms - ); - break; - } - default: - throw new Error( - `Entity type '${entity.type}' is not supported by getTermsDone.` - ); - } - - return termsPromise.then((res) => ({ entity, terms: res })); -} - -/** - * Payload creator for TERMS/GET_TERM_DETAILS_INIT action, - * which marks that we are about to fetch details of the specified term. - * If any details for another term are currently being fetched, - * they will be silently discarded. - * @param {Number|String} termId - * @return {String} - */ -function getTermDetailsInit(termId) { - return _.toString(termId); -} - -/** - * Payload creator for TERMS/GET_TERM_DETAILS_DONE action, - * which fetch details of the specified term. - * @param {Number|String} termId - * @param {String} tokenV3 - * @return {Promise} - */ -function getTermDetailsDone(termId, tokenV3) { - const service = getService(tokenV3); - return service - .getTermDetails(termId) - .then((details) => ({ termId, details })); -} - -/** - * Payload creator for TERMS/GET_DOCU_SIGN_URL_INIT - * @param {Number|String} templateId id of document template to sign - * @return {String} string format of the id - */ -function getDocuSignUrlInit(templateId) { - return _.toString(templateId); -} - -/** - * Payload creator for TERMS/GET_DOCU_SIGN_URL_DONE - * which generate the url of DoduSign term - * @param {Number|String} templateId id of document template to sign - * @param {String} returnUrl callback url after finishing singing - * @param {String} tokenV3 auth token - * @return {Promise} promise of request result - */ -function getDocuSignUrlDone(templateId, returnUrl, tokenV3) { - const service = getService(tokenV3); - return service - .getDocuSignUrl(templateId, returnUrl) - .then((resp) => ({ templateId, docuSignUrl: resp.recipientViewUrl })); -} - -/** - * Payload creator for TERMS/AGREE_TERM_INIT - * @param {Number|String} termId id of term - * @return {String} string format of the id - */ -function agreeTermInit(termId) { - return _.toString(termId); -} - -/** - * Payload creator for TERMS/AGREE_TERM_DONE - * @param {Number|String} termId id of term - * @param {String} tokenV3 auth token - * @return {Promise} promise of request result - */ -function agreeTermDone(termId, tokenV3) { - const service = getService(tokenV3); - return service - .agreeTerm(termId) - .then((resp) => ({ termId, success: resp.success })); -} - -/** - * Payload creator for TERMS/CHECK_STATUS_DONE - * which will check if all terms of specified entity have been agreed, - * - * NOTE: - * As in some reason backend does not saves immediately that DocuSign term has been agreed - * In case not all terms were agreed we try again after some delay. - * Maximum quantity attempts and delay between attempts are configured in - * MAX_ATTEMPTS and TIME_OUT - * - * TODO: - * Looks like the bug described above was caused by server caching responses - * at least for getTermDetails which is used by getCommunityTerms. - * To fix it I've added nocache random value param in the terms service - * for getTermDetails and it looks like works so we get results immediately. - * This still have to be tested for challenges as they use another endpoint - * in method getChallengeTerms. - * Also terms which use third part service DocuSign has to be also tested prior - * to removing multiple checks. - * In case their agreed status is updated immediately, this code - * has to simplified and don't make several attempts, only one. - * - * @param {Object} entity entity object - * @param {String} entity.type entity type ['challenge'||'community'] - * @param {String} entity.id entity id - * @param {Object} tokens object with tokenV2 and tokenV3 properties - * - * @return {Promise} promise of request result - */ -function checkStatusDone(entity, tokens) { - // timeout between checking status attempts - const TIME_OUT = 5000; - - // maximum attempts to check status - const MAX_ATTEMPTS = 5; - - // we set this flag for getTermsDone when MOCK_TERMS_SERVICE is true - // so that checkStatusDone resolves to all terms agreed when mocking - const mockAgreed = config.MOCK_TERMS_SERVICE; - - /** - * Promisified setTimeout - * @param {Number} timeout timeout in milliseconds - * @return {Promise} resolves after timeout - */ - const delay = (timeout) => - new Promise((resolve) => { - setTimeout(resolve, timeout); - }); - - /** - * Makes attempt to check status - * @param {Number} maxAttempts maximum number of attempts to perform - * @return {Promise} resolves to the list of term objects - */ - const checkStatus = (maxAttempts) => - getTermsDone(entity, tokens, mockAgreed).then((res) => { - const allAgreed = _.every(res.terms, "agreed"); - - // if not all terms are agreed and we still have some attempts to try - if (!allAgreed && maxAttempts > 1) { - return delay(TIME_OUT).then(() => checkStatus(maxAttempts - 1)); - } - - return res.terms; - }); - - return checkStatus(MAX_ATTEMPTS); -} - -/** - * Payload creator for the action that opens the specified terms modal. - * @param {String} modalInstanceUuid ID of the terms modal instance to be - * opened. Any other instances of terms modals present in the page will be - * closed automatically by this action, as it is not safe to open multiple - * modals, (and makes no sense in current implementation). - * @param {???} selectedTerm Optional. Selected term. It was not documented by - * author of related code, thus the exact value is not clear. - * @return {Object} Action payload. - */ -function openTermsModal(modalInstanceUuid, selectedTerm) { - return { modalInstanceUuid, selectedTerm }; -} - -/** - * Payload creator for the action that closes the specified terms modal. - * @param {String} modalInstanceUuid ID of the terms modal instance to be - * closed. If another terms modal is open, it won't be affected. - * @return {String} Action payload. - */ -function closeTermsModal(modalInstanceUuid) { - return modalInstanceUuid; -} - -export default createActions({ - TERMS: { - GET_TERMS_INIT: _.identity, - GET_TERMS_DONE: getTermsDone, - GET_TERM_DETAILS_INIT: getTermDetailsInit, - GET_TERM_DETAILS_DONE: getTermDetailsDone, - GET_DOCU_SIGN_URL_INIT: getDocuSignUrlInit, - GET_DOCU_SIGN_URL_DONE: getDocuSignUrlDone, - AGREE_TERM_INIT: agreeTermInit, - AGREE_TERM_DONE: agreeTermDone, - - OPEN_TERMS_MODAL: openTermsModal, - CLOSE_TERMS_MODAL: closeTermsModal, - - SELECT_TERM: _.identity, - SIGN_DOCU: _.identity, - CHECK_STATUS_INIT: _.noop, - CHECK_STATUS_DONE: checkStatusDone, - }, -}); diff --git a/src/apps/earn/src/actions/user/creators.js b/src/apps/earn/src/actions/user/creators.js deleted file mode 100644 index 14aa5d151..000000000 --- a/src/apps/earn/src/actions/user/creators.js +++ /dev/null @@ -1,15 +0,0 @@ -import { createActions } from "redux-actions"; -import * as ACTION_TYPE from "./types"; - -const actions = createActions( - {}, - ACTION_TYPE.LOAD_PROFILE_ERROR, - ACTION_TYPE.LOAD_PROFILE_PENDING, - ACTION_TYPE.LOAD_PROFILE_SUCCESS, - ACTION_TYPE.LOAD_REFERRAL_DATA_ERROR, - ACTION_TYPE.LOAD_REFERRAL_DATA_PENDING, - ACTION_TYPE.LOAD_REFERRAL_DATA_SUCCESS, - { prefix: "USER", namespace: "--" } -); - -export default actions; diff --git a/src/apps/earn/src/actions/user/effectors.js b/src/apps/earn/src/actions/user/effectors.js deleted file mode 100644 index 5a0449eba..000000000 --- a/src/apps/earn/src/actions/user/effectors.js +++ /dev/null @@ -1,75 +0,0 @@ -import { profileGetLoggedInAsync } from "~/libs/core"; - -import * as selectors from "../../reducers/user/selectors"; -import { fetchReferralData } from "../../services/referral"; -import { delay } from "../../utils/misc"; - -import actions from "./creators"; - -/** - * Loads user's referral data. - * - * @param {Object} store redux store object - * @param {boolean} [forceReload] force data reload even if it is already present - * @returns {Promise} - */ -export const loadReferralData = async ( - { dispatch, getState }, - forceReload = false -) => { - const state = getState(); - const referralData = selectors.getReferralData(state); - if (referralData && !forceReload) { - return; - } - const profile = selectors.getProfile(state); - if (!profile) { - console.error( - "No profile data provided when trying to fetch referral data." - ); - return; - } - let data = null; - try { - data = await fetchReferralData(profile); - } catch (error) { - dispatch(actions.loadReferralDataError(error.toString())); - return; - } - dispatch(actions.loadReferralDataSuccess(data)); -}; - -/** - * Tries to load user's profile. - * - * @param {Object} store redux store object - * @param {boolean} [forceReload] force data reload even if it is already present - * @returns {Promise} - */ -export const loadProfile = async ( - { dispatch, getState }, - forceReload = false -) => { - let profile = selectors.getProfile(getState()); - if (profile && !forceReload) { - return; - } - let error = null; - dispatch(actions.loadProfilePending()); - for (let i = 0; i < 3; i++) { - try { - profile = await profileGetLoggedInAsync(); - } catch (err) { - error = err; - } - if (profile) { - break; - } - await delay(1000); - } - if (error) { - dispatch(actions.loadProfileError(error.toString())); - } else { - dispatch(actions.loadProfileSuccess(profile)); - } -}; diff --git a/src/apps/earn/src/actions/user/types.js b/src/apps/earn/src/actions/user/types.js deleted file mode 100644 index 1ae4310a9..000000000 --- a/src/apps/earn/src/actions/user/types.js +++ /dev/null @@ -1,6 +0,0 @@ -export const LOAD_PROFILE_PENDING = "LOAD_PROFILE_PENDING"; -export const LOAD_PROFILE_ERROR = "LOAD_PROFILE_ERROR"; -export const LOAD_PROFILE_SUCCESS = "LOAD_PROFILE_SUCCESS"; -export const LOAD_REFERRAL_DATA_ERROR = "LOAD_REFERRAL_DATA_ERROR"; -export const LOAD_REFERRAL_DATA_PENDING = "LOAD_REFERRAL_DATA_PENDING"; -export const LOAD_REFERRAL_DATA_SUCCESS = "LOAD_REFERRAL_DATA_SUCCESS"; diff --git a/src/apps/earn/src/assets/icons/IconCloudDownload.svg b/src/apps/earn/src/assets/icons/IconCloudDownload.svg deleted file mode 100644 index b17e3bc52..000000000 --- a/src/apps/earn/src/assets/icons/IconCloudDownload.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/IconMinimalDown.svg b/src/apps/earn/src/assets/icons/IconMinimalDown.svg deleted file mode 100644 index 919a0ae0b..000000000 --- a/src/apps/earn/src/assets/icons/IconMinimalDown.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/src/apps/earn/src/assets/icons/IconMinimalLeft.svg b/src/apps/earn/src/assets/icons/IconMinimalLeft.svg deleted file mode 100644 index 287bf690e..000000000 --- a/src/apps/earn/src/assets/icons/IconMinimalLeft.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/src/apps/earn/src/assets/icons/IconMinimalRight.svg b/src/apps/earn/src/assets/icons/IconMinimalRight.svg deleted file mode 100644 index d2905c94b..000000000 --- a/src/apps/earn/src/assets/icons/IconMinimalRight.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/src/apps/earn/src/assets/icons/IconMinimalUp.svg b/src/apps/earn/src/assets/icons/IconMinimalUp.svg deleted file mode 100644 index 9bf3e2d5a..000000000 --- a/src/apps/earn/src/assets/icons/IconMinimalUp.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/src/apps/earn/src/assets/icons/IconShare.svg b/src/apps/earn/src/assets/icons/IconShare.svg deleted file mode 100644 index 2c731ab66..000000000 --- a/src/apps/earn/src/assets/icons/IconShare.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/src/apps/earn/src/assets/icons/IconSquareDownload.svg b/src/apps/earn/src/assets/icons/IconSquareDownload.svg deleted file mode 100644 index 9ef5ceed7..000000000 --- a/src/apps/earn/src/assets/icons/IconSquareDownload.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/IconTrashSimple.svg b/src/apps/earn/src/assets/icons/IconTrashSimple.svg deleted file mode 100644 index 2b1e18cf4..000000000 --- a/src/apps/earn/src/assets/icons/IconTrashSimple.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - diff --git a/src/apps/earn/src/assets/icons/arrow-down.svg b/src/apps/earn/src/assets/icons/arrow-down.svg deleted file mode 100644 index bcfb1072d..000000000 --- a/src/apps/earn/src/assets/icons/arrow-down.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/arrow-prev.svg b/src/apps/earn/src/assets/icons/arrow-prev.svg deleted file mode 100644 index 0cfb59fda..000000000 --- a/src/apps/earn/src/assets/icons/arrow-prev.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/src/apps/earn/src/assets/icons/arrow-right.svg b/src/apps/earn/src/assets/icons/arrow-right.svg deleted file mode 100644 index a75d8d921..000000000 --- a/src/apps/earn/src/assets/icons/arrow-right.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/apps/earn/src/assets/icons/arrow.svg b/src/apps/earn/src/assets/icons/arrow.svg deleted file mode 100644 index 5a6667ff4..000000000 --- a/src/apps/earn/src/assets/icons/arrow.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - 2F48C66D-6E88-40BD-8F0F-1ABB211BC6D0 - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/banner-chevron-up.svg b/src/apps/earn/src/assets/icons/banner-chevron-up.svg deleted file mode 100644 index 96c5c3d89..000000000 --- a/src/apps/earn/src/assets/icons/banner-chevron-up.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - banner chevron - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/card-view.svg b/src/apps/earn/src/assets/icons/card-view.svg deleted file mode 100644 index a25ef8005..000000000 --- a/src/apps/earn/src/assets/icons/card-view.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/checkmark-large.png b/src/apps/earn/src/assets/icons/checkmark-large.png deleted file mode 100644 index d3c90d8c6..000000000 Binary files a/src/apps/earn/src/assets/icons/checkmark-large.png and /dev/null differ diff --git a/src/apps/earn/src/assets/icons/checkmark-medium.png b/src/apps/earn/src/assets/icons/checkmark-medium.png deleted file mode 100644 index 454fd20e7..000000000 Binary files a/src/apps/earn/src/assets/icons/checkmark-medium.png and /dev/null differ diff --git a/src/apps/earn/src/assets/icons/checkmark-small.png b/src/apps/earn/src/assets/icons/checkmark-small.png deleted file mode 100644 index fdcd176f2..000000000 Binary files a/src/apps/earn/src/assets/icons/checkmark-small.png and /dev/null differ diff --git a/src/apps/earn/src/assets/icons/checkpoint-small.svg b/src/apps/earn/src/assets/icons/checkpoint-small.svg deleted file mode 100644 index 81f1073b6..000000000 --- a/src/apps/earn/src/assets/icons/checkpoint-small.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/checkpoint.svg b/src/apps/earn/src/assets/icons/checkpoint.svg deleted file mode 100644 index 5f748e847..000000000 --- a/src/apps/earn/src/assets/icons/checkpoint.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/close-gray.svg b/src/apps/earn/src/assets/icons/close-gray.svg deleted file mode 100644 index d773daa00..000000000 --- a/src/apps/earn/src/assets/icons/close-gray.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/close.svg b/src/apps/earn/src/assets/icons/close.svg deleted file mode 100644 index 26c8dfbb8..000000000 --- a/src/apps/earn/src/assets/icons/close.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/dropdown-arrow.png b/src/apps/earn/src/assets/icons/dropdown-arrow.png deleted file mode 100644 index 40edda675..000000000 Binary files a/src/apps/earn/src/assets/icons/dropdown-arrow.png and /dev/null differ diff --git a/src/apps/earn/src/assets/icons/find-work-green.svg b/src/apps/earn/src/assets/icons/find-work-green.svg deleted file mode 100644 index fb4743f1a..000000000 --- a/src/apps/earn/src/assets/icons/find-work-green.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - 1D5681BC-C33F-4401-A8C9-A5A6F99E8CA7 - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/find-work.svg b/src/apps/earn/src/assets/icons/find-work.svg deleted file mode 100644 index 1af6abbaa..000000000 --- a/src/apps/earn/src/assets/icons/find-work.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - DAD5564D-C4AF-448B-9138-756872CA4CB8 - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/icon-calendar-blue.svg b/src/apps/earn/src/assets/icons/icon-calendar-blue.svg deleted file mode 100644 index 84fbbc9dd..000000000 --- a/src/apps/earn/src/assets/icons/icon-calendar-blue.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - Fill 81 Copy - Created with Sketch. - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/icon-calendar-medium.svg b/src/apps/earn/src/assets/icons/icon-calendar-medium.svg deleted file mode 100644 index 26c0a56e8..000000000 --- a/src/apps/earn/src/assets/icons/icon-calendar-medium.svg +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/src/apps/earn/src/assets/icons/icon-calendar.svg b/src/apps/earn/src/assets/icons/icon-calendar.svg deleted file mode 100644 index b75a6fb03..000000000 --- a/src/apps/earn/src/assets/icons/icon-calendar.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - Fill 81 Copy - Created with Sketch. - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/icon-corner-left-green.svg b/src/apps/earn/src/assets/icons/icon-corner-left-green.svg deleted file mode 100644 index 0b69acfa2..000000000 --- a/src/apps/earn/src/assets/icons/icon-corner-left-green.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - diff --git a/src/apps/earn/src/assets/icons/icon-cross.png b/src/apps/earn/src/assets/icons/icon-cross.png deleted file mode 100644 index 75604cde4..000000000 Binary files a/src/apps/earn/src/assets/icons/icon-cross.png and /dev/null differ diff --git a/src/apps/earn/src/assets/icons/icon-facebook-gray.svg b/src/apps/earn/src/assets/icons/icon-facebook-gray.svg deleted file mode 100644 index fd976e999..000000000 --- a/src/apps/earn/src/assets/icons/icon-facebook-gray.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - diff --git a/src/apps/earn/src/assets/icons/icon-gear-blue.svg b/src/apps/earn/src/assets/icons/icon-gear-blue.svg deleted file mode 100644 index 6705fa5ba..000000000 --- a/src/apps/earn/src/assets/icons/icon-gear-blue.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/src/apps/earn/src/assets/icons/icon-globe-simple.svg b/src/apps/earn/src/assets/icons/icon-globe-simple.svg deleted file mode 100644 index c726e199b..000000000 --- a/src/apps/earn/src/assets/icons/icon-globe-simple.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - diff --git a/src/apps/earn/src/assets/icons/icon-globe.png b/src/apps/earn/src/assets/icons/icon-globe.png deleted file mode 100644 index d324710cd..000000000 Binary files a/src/apps/earn/src/assets/icons/icon-globe.png and /dev/null differ diff --git a/src/apps/earn/src/assets/icons/icon-hourglass.svg b/src/apps/earn/src/assets/icons/icon-hourglass.svg deleted file mode 100644 index 9a154d2d0..000000000 --- a/src/apps/earn/src/assets/icons/icon-hourglass.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - diff --git a/src/apps/earn/src/assets/icons/icon-linkedin-gray.svg b/src/apps/earn/src/assets/icons/icon-linkedin-gray.svg deleted file mode 100644 index 6653b4d77..000000000 --- a/src/apps/earn/src/assets/icons/icon-linkedin-gray.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - diff --git a/src/apps/earn/src/assets/icons/icon-location-crimson.svg b/src/apps/earn/src/assets/icons/icon-location-crimson.svg deleted file mode 100644 index d993f5f6f..000000000 --- a/src/apps/earn/src/assets/icons/icon-location-crimson.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - diff --git a/src/apps/earn/src/assets/icons/icon-location-mark.svg b/src/apps/earn/src/assets/icons/icon-location-mark.svg deleted file mode 100644 index 19c17a8a9..000000000 --- a/src/apps/earn/src/assets/icons/icon-location-mark.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - diff --git a/src/apps/earn/src/assets/icons/icon-magnifier.svg b/src/apps/earn/src/assets/icons/icon-magnifier.svg deleted file mode 100644 index 657f19399..000000000 --- a/src/apps/earn/src/assets/icons/icon-magnifier.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - diff --git a/src/apps/earn/src/assets/icons/icon-mark.png b/src/apps/earn/src/assets/icons/icon-mark.png deleted file mode 100644 index 8b7b43c57..000000000 Binary files a/src/apps/earn/src/assets/icons/icon-mark.png and /dev/null differ diff --git a/src/apps/earn/src/assets/icons/icon-next.svg b/src/apps/earn/src/assets/icons/icon-next.svg deleted file mode 100644 index 467efa406..000000000 --- a/src/apps/earn/src/assets/icons/icon-next.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/icon-number-one.png b/src/apps/earn/src/assets/icons/icon-number-one.png deleted file mode 100644 index 29c75b631..000000000 Binary files a/src/apps/earn/src/assets/icons/icon-number-one.png and /dev/null differ diff --git a/src/apps/earn/src/assets/icons/icon-number-three.png b/src/apps/earn/src/assets/icons/icon-number-three.png deleted file mode 100644 index 9b3eb12c3..000000000 Binary files a/src/apps/earn/src/assets/icons/icon-number-three.png and /dev/null differ diff --git a/src/apps/earn/src/assets/icons/icon-number-two.png b/src/apps/earn/src/assets/icons/icon-number-two.png deleted file mode 100644 index 4947bef6a..000000000 Binary files a/src/apps/earn/src/assets/icons/icon-number-two.png and /dev/null differ diff --git a/src/apps/earn/src/assets/icons/icon-payment.svg b/src/apps/earn/src/assets/icons/icon-payment.svg deleted file mode 100644 index cdc79ef20..000000000 --- a/src/apps/earn/src/assets/icons/icon-payment.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - -$ - diff --git a/src/apps/earn/src/assets/icons/icon-prev.svg b/src/apps/earn/src/assets/icons/icon-prev.svg deleted file mode 100644 index 3e90c4708..000000000 --- a/src/apps/earn/src/assets/icons/icon-prev.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/icon-sad-face.svg b/src/apps/earn/src/assets/icons/icon-sad-face.svg deleted file mode 100644 index b9b685c03..000000000 --- a/src/apps/earn/src/assets/icons/icon-sad-face.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - 838C3DF4-2CF1-42A3-BC49-21142AF2003C@2x - Created with sketchtool. - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/icon-tick-circled.svg b/src/apps/earn/src/assets/icons/icon-tick-circled.svg deleted file mode 100644 index 5d80b5f34..000000000 --- a/src/apps/earn/src/assets/icons/icon-tick-circled.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/icon-twitter-gray.svg b/src/apps/earn/src/assets/icons/icon-twitter-gray.svg deleted file mode 100644 index ca0065e2b..000000000 --- a/src/apps/earn/src/assets/icons/icon-twitter-gray.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - diff --git a/src/apps/earn/src/assets/icons/info.svg b/src/apps/earn/src/assets/icons/info.svg deleted file mode 100644 index 937844107..000000000 --- a/src/apps/earn/src/assets/icons/info.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/list-view.svg b/src/apps/earn/src/assets/icons/list-view.svg deleted file mode 100644 index ea5bd2186..000000000 --- a/src/apps/earn/src/assets/icons/list-view.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/logo_topcoder.svg b/src/apps/earn/src/assets/icons/logo_topcoder.svg deleted file mode 100644 index 3fa9d3aa6..000000000 --- a/src/apps/earn/src/assets/icons/logo_topcoder.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/long-arrow-next.svg b/src/apps/earn/src/assets/icons/long-arrow-next.svg deleted file mode 100644 index 05de31982..000000000 --- a/src/apps/earn/src/assets/icons/long-arrow-next.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/apps/earn/src/assets/icons/menu-chevron-up.svg b/src/apps/earn/src/assets/icons/menu-chevron-up.svg deleted file mode 100644 index cfdb74002..000000000 --- a/src/apps/earn/src/assets/icons/menu-chevron-up.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - CE319284-3A67-4756-B6AD-2D4164352632 - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/my-work-green.svg b/src/apps/earn/src/assets/icons/my-work-green.svg deleted file mode 100644 index 3b944366f..000000000 --- a/src/apps/earn/src/assets/icons/my-work-green.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - 9178F574-02BA-4DFF-A4C4-93CE92BDE405 - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/my-work.svg b/src/apps/earn/src/assets/icons/my-work.svg deleted file mode 100644 index b679bbba3..000000000 --- a/src/apps/earn/src/assets/icons/my-work.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - C087C319-6EC3-4D78-A758-D1B398901B5D - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/not-found-recommended.png b/src/apps/earn/src/assets/icons/not-found-recommended.png deleted file mode 100644 index 728a77b96..000000000 Binary files a/src/apps/earn/src/assets/icons/not-found-recommended.png and /dev/null differ diff --git a/src/apps/earn/src/assets/icons/not-found.png b/src/apps/earn/src/assets/icons/not-found.png deleted file mode 100644 index a11d6a87b..000000000 Binary files a/src/apps/earn/src/assets/icons/not-found.png and /dev/null differ diff --git a/src/apps/earn/src/assets/icons/note.svg b/src/apps/earn/src/assets/icons/note.svg deleted file mode 100644 index 5edeea9af..000000000 --- a/src/apps/earn/src/assets/icons/note.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/registrant.svg b/src/apps/earn/src/assets/icons/registrant.svg deleted file mode 100644 index 1e337b068..000000000 --- a/src/apps/earn/src/assets/icons/registrant.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/ribbon-icon.svg b/src/apps/earn/src/assets/icons/ribbon-icon.svg deleted file mode 100644 index 92bef26e0..000000000 --- a/src/apps/earn/src/assets/icons/ribbon-icon.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/robot-embarassed.svg b/src/apps/earn/src/assets/icons/robot-embarassed.svg deleted file mode 100644 index 0d974364a..000000000 --- a/src/apps/earn/src/assets/icons/robot-embarassed.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - robot-embarresed - Created with Sketch. - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/robot-happy.svg b/src/apps/earn/src/assets/icons/robot-happy.svg deleted file mode 100644 index 8567e7369..000000000 --- a/src/apps/earn/src/assets/icons/robot-happy.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/search.svg b/src/apps/earn/src/assets/icons/search.svg deleted file mode 100644 index 2ae8f711a..000000000 --- a/src/apps/earn/src/assets/icons/search.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/submission.svg b/src/apps/earn/src/assets/icons/submission.svg deleted file mode 100644 index 2ddc02eb2..000000000 --- a/src/apps/earn/src/assets/icons/submission.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/track-des.svg b/src/apps/earn/src/assets/icons/track-des.svg deleted file mode 100644 index fee5c04d2..000000000 --- a/src/apps/earn/src/assets/icons/track-des.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - AC4D4B5E-93F9-4F74-A2B8-9AE81B345347 - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/track-dev.svg b/src/apps/earn/src/assets/icons/track-dev.svg deleted file mode 100644 index f30f2f2d1..000000000 --- a/src/apps/earn/src/assets/icons/track-dev.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - 3D983778-B2BF-4CB8-B636-1C9EDCAA0EFD - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/track-ds.svg b/src/apps/earn/src/assets/icons/track-ds.svg deleted file mode 100644 index e676bfd80..000000000 --- a/src/apps/earn/src/assets/icons/track-ds.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - DE0551FC-97AC-4CB0-9254-C3C58445CA86 - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/track-qa.svg b/src/apps/earn/src/assets/icons/track-qa.svg deleted file mode 100644 index 18db8e841..000000000 --- a/src/apps/earn/src/assets/icons/track-qa.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - 37873904-C546-401A-A25C-FFDA30980031 - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/icons/update-success.svg b/src/apps/earn/src/assets/icons/update-success.svg deleted file mode 100644 index a721c4f61..000000000 --- a/src/apps/earn/src/assets/icons/update-success.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/IconSquareDownload.svg b/src/apps/earn/src/assets/images/IconSquareDownload.svg deleted file mode 100644 index 9ef5ceed7..000000000 --- a/src/apps/earn/src/assets/images/IconSquareDownload.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/arrow-down.svg b/src/apps/earn/src/assets/images/arrow-down.svg deleted file mode 100644 index d53eedd1a..000000000 --- a/src/apps/earn/src/assets/images/arrow-down.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - 5D558F9B-43EB-41DF-905D-8862EFEE8959 - Created with sketchtool. - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/arrow-left.svg b/src/apps/earn/src/assets/images/arrow-left.svg deleted file mode 100644 index 29f5eee2f..000000000 --- a/src/apps/earn/src/assets/images/arrow-left.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - AB093E0F-1C19-4224-9B2F-A8D7A5A76AD0 - Created with sketchtool. - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/arrow-next.svg b/src/apps/earn/src/assets/images/arrow-next.svg deleted file mode 100644 index 288a45180..000000000 --- a/src/apps/earn/src/assets/images/arrow-next.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/arrow-prev-green.svg b/src/apps/earn/src/assets/images/arrow-prev-green.svg deleted file mode 100644 index c0c10c4df..000000000 --- a/src/apps/earn/src/assets/images/arrow-prev-green.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/apps/earn/src/assets/images/arrow-prev.svg b/src/apps/earn/src/assets/images/arrow-prev.svg deleted file mode 100644 index 4c17ae49f..000000000 --- a/src/apps/earn/src/assets/images/arrow-prev.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/assets/images/arrow-right-green.svg b/src/apps/earn/src/assets/images/arrow-right-green.svg deleted file mode 100644 index 2c28fcca4..000000000 --- a/src/apps/earn/src/assets/images/arrow-right-green.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/assets/images/blob-purple.svg b/src/apps/earn/src/assets/images/blob-purple.svg deleted file mode 100644 index cd1d5d93c..000000000 --- a/src/apps/earn/src/assets/images/blob-purple.svg +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/blob-yellow.svg b/src/apps/earn/src/assets/images/blob-yellow.svg deleted file mode 100644 index 98ff027a5..000000000 --- a/src/apps/earn/src/assets/images/blob-yellow.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - 4E19AD87-834F-4DB6-AFFB-EA530C3ACC5D - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/calendar.svg b/src/apps/earn/src/assets/images/calendar.svg deleted file mode 100644 index d14c5596e..000000000 --- a/src/apps/earn/src/assets/images/calendar.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/apps/earn/src/assets/images/celebrate.svg b/src/apps/earn/src/assets/images/celebrate.svg deleted file mode 100644 index be86c1da7..000000000 --- a/src/apps/earn/src/assets/images/celebrate.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - Combined Shape - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/completed.svg b/src/apps/earn/src/assets/images/completed.svg deleted file mode 100644 index 8b638020e..000000000 --- a/src/apps/earn/src/assets/images/completed.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - C23ADC25-9150-4E3C-9043-97A704EBE690 - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/default-user-avatar.svg b/src/apps/earn/src/assets/images/default-user-avatar.svg deleted file mode 100644 index 4a80a0465..000000000 --- a/src/apps/earn/src/assets/images/default-user-avatar.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/ico-user-default.png b/src/apps/earn/src/assets/images/ico-user-default.png deleted file mode 100644 index 6454881d6..000000000 Binary files a/src/apps/earn/src/assets/images/ico-user-default.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/ico-user-default.svg b/src/apps/earn/src/assets/images/ico-user-default.svg deleted file mode 100644 index ed6ce1a7b..000000000 --- a/src/apps/earn/src/assets/images/ico-user-default.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - ico-user-default - Created with Sketch. - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/icon-arrow-down-black.svg b/src/apps/earn/src/assets/images/icon-arrow-down-black.svg deleted file mode 100644 index 869b8991c..000000000 --- a/src/apps/earn/src/assets/images/icon-arrow-down-black.svg +++ /dev/null @@ -1,9 +0,0 @@ - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/icon-arrow-down.svg b/src/apps/earn/src/assets/images/icon-arrow-down.svg deleted file mode 100644 index b8814cfb9..000000000 --- a/src/apps/earn/src/assets/images/icon-arrow-down.svg +++ /dev/null @@ -1,9 +0,0 @@ - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/icon-arrow-up-black.svg b/src/apps/earn/src/assets/images/icon-arrow-up-black.svg deleted file mode 100644 index 3b6328bbb..000000000 --- a/src/apps/earn/src/assets/images/icon-arrow-up-black.svg +++ /dev/null @@ -1,9 +0,0 @@ - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/icon-arrow-up.svg b/src/apps/earn/src/assets/images/icon-arrow-up.svg deleted file mode 100644 index cae45cce0..000000000 --- a/src/apps/earn/src/assets/images/icon-arrow-up.svg +++ /dev/null @@ -1,9 +0,0 @@ - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/icon-calendar-2-active.svg b/src/apps/earn/src/assets/images/icon-calendar-2-active.svg deleted file mode 100644 index e03bcbecf..000000000 --- a/src/apps/earn/src/assets/images/icon-calendar-2-active.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/assets/images/icon-calendar-2.svg b/src/apps/earn/src/assets/images/icon-calendar-2.svg deleted file mode 100644 index c8f954975..000000000 --- a/src/apps/earn/src/assets/images/icon-calendar-2.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/assets/images/icon-close-green.svg b/src/apps/earn/src/assets/images/icon-close-green.svg deleted file mode 100644 index 101c548ea..000000000 --- a/src/apps/earn/src/assets/images/icon-close-green.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/assets/images/icon-date-sort.svg b/src/apps/earn/src/assets/images/icon-date-sort.svg deleted file mode 100644 index 1defb57d7..000000000 --- a/src/apps/earn/src/assets/images/icon-date-sort.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/apps/earn/src/assets/images/icon-sort-mobile.svg b/src/apps/earn/src/assets/images/icon-sort-mobile.svg deleted file mode 100644 index d5c9466c8..000000000 --- a/src/apps/earn/src/assets/images/icon-sort-mobile.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/apps/earn/src/assets/images/icon-sort.svg b/src/apps/earn/src/assets/images/icon-sort.svg deleted file mode 100644 index 8919cf0ed..000000000 --- a/src/apps/earn/src/assets/images/icon-sort.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/assets/images/icon-time-active.svg b/src/apps/earn/src/assets/images/icon-time-active.svg deleted file mode 100644 index cf1202494..000000000 --- a/src/apps/earn/src/assets/images/icon-time-active.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/assets/images/icon-time.svg b/src/apps/earn/src/assets/images/icon-time.svg deleted file mode 100644 index 5e535625f..000000000 --- a/src/apps/earn/src/assets/images/icon-time.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/assets/images/icon-verified.svg b/src/apps/earn/src/assets/images/icon-verified.svg deleted file mode 100644 index ac4a28ac7..000000000 --- a/src/apps/earn/src/assets/images/icon-verified.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - Group 6 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/apps/earn/src/assets/images/nav-active-item-blue.svg b/src/apps/earn/src/assets/images/nav-active-item-blue.svg deleted file mode 100644 index 459058553..000000000 --- a/src/apps/earn/src/assets/images/nav-active-item-blue.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/apps/earn/src/assets/images/nav-active-item.svg b/src/apps/earn/src/assets/images/nav-active-item.svg deleted file mode 100644 index 5ff4ba65b..000000000 --- a/src/apps/earn/src/assets/images/nav-active-item.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/apps/earn/src/assets/images/open-graph/challenges/._06-Big-Prize.png b/src/apps/earn/src/assets/images/open-graph/challenges/._06-Big-Prize.png deleted file mode 100644 index 127aed183..000000000 Binary files a/src/apps/earn/src/assets/images/open-graph/challenges/._06-Big-Prize.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/open-graph/challenges/._09-First2Finish.png b/src/apps/earn/src/assets/images/open-graph/challenges/._09-First2Finish.png deleted file mode 100644 index 8a66ae8d6..000000000 Binary files a/src/apps/earn/src/assets/images/open-graph/challenges/._09-First2Finish.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/open-graph/challenges/02-Design-Preview.png b/src/apps/earn/src/assets/images/open-graph/challenges/02-Design-Preview.png deleted file mode 100644 index 18ddf6c07..000000000 Binary files a/src/apps/earn/src/assets/images/open-graph/challenges/02-Design-Preview.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/open-graph/challenges/03-Development.png b/src/apps/earn/src/assets/images/open-graph/challenges/03-Development.png deleted file mode 100644 index 11d31928d..000000000 Binary files a/src/apps/earn/src/assets/images/open-graph/challenges/03-Development.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/open-graph/challenges/04-Data-Science.png b/src/apps/earn/src/assets/images/open-graph/challenges/04-Data-Science.png deleted file mode 100644 index 5f58f3e14..000000000 Binary files a/src/apps/earn/src/assets/images/open-graph/challenges/04-Data-Science.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/open-graph/challenges/05-QA.png b/src/apps/earn/src/assets/images/open-graph/challenges/05-QA.png deleted file mode 100644 index a476e36c8..000000000 Binary files a/src/apps/earn/src/assets/images/open-graph/challenges/05-QA.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/open-graph/challenges/06-Big-Prize.png b/src/apps/earn/src/assets/images/open-graph/challenges/06-Big-Prize.png deleted file mode 100644 index 8fedf92a1..000000000 Binary files a/src/apps/earn/src/assets/images/open-graph/challenges/06-Big-Prize.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/open-graph/challenges/09-First2Finish.png b/src/apps/earn/src/assets/images/open-graph/challenges/09-First2Finish.png deleted file mode 100644 index 07993a41a..000000000 Binary files a/src/apps/earn/src/assets/images/open-graph/challenges/09-First2Finish.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/open-graph/challenges/Design-First2Finish.png b/src/apps/earn/src/assets/images/open-graph/challenges/Design-First2Finish.png deleted file mode 100644 index 887800120..000000000 Binary files a/src/apps/earn/src/assets/images/open-graph/challenges/Design-First2Finish.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/open-graph/challenges/Design-Task.png b/src/apps/earn/src/assets/images/open-graph/challenges/Design-Task.png deleted file mode 100644 index f2e2fb18e..000000000 Binary files a/src/apps/earn/src/assets/images/open-graph/challenges/Design-Task.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/open-graph/challenges/Development-First2Finish.png b/src/apps/earn/src/assets/images/open-graph/challenges/Development-First2Finish.png deleted file mode 100644 index f3f2ceee8..000000000 Binary files a/src/apps/earn/src/assets/images/open-graph/challenges/Development-First2Finish.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/open-graph/challenges/Development-Task.png b/src/apps/earn/src/assets/images/open-graph/challenges/Development-Task.png deleted file mode 100644 index 20cbac13f..000000000 Binary files a/src/apps/earn/src/assets/images/open-graph/challenges/Development-Task.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/open-graph/challenges/MM-Challenge.png b/src/apps/earn/src/assets/images/open-graph/challenges/MM-Challenge.png deleted file mode 100644 index b747546df..000000000 Binary files a/src/apps/earn/src/assets/images/open-graph/challenges/MM-Challenge.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/open-graph/challenges/QA-First2Finish.png b/src/apps/earn/src/assets/images/open-graph/challenges/QA-First2Finish.png deleted file mode 100644 index e3d439288..000000000 Binary files a/src/apps/earn/src/assets/images/open-graph/challenges/QA-First2Finish.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/open-graph/challenges/QA-Task.png b/src/apps/earn/src/assets/images/open-graph/challenges/QA-Task.png deleted file mode 100644 index 0c94fdc20..000000000 Binary files a/src/apps/earn/src/assets/images/open-graph/challenges/QA-Task.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/progress-bar-mid.svg b/src/apps/earn/src/assets/images/progress-bar-mid.svg deleted file mode 100644 index 89bb89a9e..000000000 --- a/src/apps/earn/src/assets/images/progress-bar-mid.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - 950E0BBC-2C06-4F3E-A318-363E03868587@2x - - - - - - - - - - - - - - - - - - - - - - You are here - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/progress-bar-mobile.svg b/src/apps/earn/src/assets/images/progress-bar-mobile.svg deleted file mode 100644 index f3bc32823..000000000 --- a/src/apps/earn/src/assets/images/progress-bar-mobile.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - 4C8194C6-558E-4D9A-B419-CA20B73B5E59@2x - - - - - - - - - - - - - - - - - - - - - - - - You are here - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/progress-bar.svg b/src/apps/earn/src/assets/images/progress-bar.svg deleted file mode 100644 index 31d0152d6..000000000 --- a/src/apps/earn/src/assets/images/progress-bar.svg +++ /dev/null @@ -1,35 +0,0 @@ - - - E3BD476A-D75E-46B7-B760-74F655D7902F - - - - - - - - - - - - - - - - - - - - - - You are here - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/social.png b/src/apps/earn/src/assets/images/social.png deleted file mode 100644 index f64e59923..000000000 Binary files a/src/apps/earn/src/assets/images/social.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/social/icon_email.svg b/src/apps/earn/src/assets/images/social/icon_email.svg deleted file mode 100644 index e03bb5c54..000000000 --- a/src/apps/earn/src/assets/images/social/icon_email.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/src/apps/earn/src/assets/images/social/icon_facebook.svg b/src/apps/earn/src/assets/images/social/icon_facebook.svg deleted file mode 100644 index 548ec26a8..000000000 --- a/src/apps/earn/src/assets/images/social/icon_facebook.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/apps/earn/src/assets/images/social/icon_plus.svg b/src/apps/earn/src/assets/images/social/icon_plus.svg deleted file mode 100644 index dbb6e5ce5..000000000 --- a/src/apps/earn/src/assets/images/social/icon_plus.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/apps/earn/src/assets/images/social/icon_print.svg b/src/apps/earn/src/assets/images/social/icon_print.svg deleted file mode 100644 index aa0b1e8ea..000000000 --- a/src/apps/earn/src/assets/images/social/icon_print.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - icon print - Created with Sketch. - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/social/icon_twitter.svg b/src/apps/earn/src/assets/images/social/icon_twitter.svg deleted file mode 100644 index d08c276e0..000000000 --- a/src/apps/earn/src/assets/images/social/icon_twitter.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/apps/earn/src/assets/images/tco19_logo_black.svg b/src/apps/earn/src/assets/images/tco19_logo_black.svg deleted file mode 100644 index 02102ed27..000000000 --- a/src/apps/earn/src/assets/images/tco19_logo_black.svg +++ /dev/null @@ -1,39 +0,0 @@ - - - - Topcoder Open 2019 - Created with sketchtool. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/thinking-face-laptop-tablet.svg b/src/apps/earn/src/assets/images/thinking-face-laptop-tablet.svg deleted file mode 100644 index 809839b5e..000000000 --- a/src/apps/earn/src/assets/images/thinking-face-laptop-tablet.svg +++ /dev/null @@ -1,433 +0,0 @@ - - - - - - - - - - - diff --git a/src/apps/earn/src/assets/images/thinking-face-mobile.svg b/src/apps/earn/src/assets/images/thinking-face-mobile.svg deleted file mode 100644 index cb818ced0..000000000 --- a/src/apps/earn/src/assets/images/thinking-face-mobile.svg +++ /dev/null @@ -1,433 +0,0 @@ - - - - - - - - - - - diff --git a/src/apps/earn/src/assets/images/tick-big.png b/src/apps/earn/src/assets/images/tick-big.png deleted file mode 100644 index 8bd83bc41..000000000 Binary files a/src/apps/earn/src/assets/images/tick-big.png and /dev/null differ diff --git a/src/apps/earn/src/assets/images/tooltip-arrow.svg b/src/apps/earn/src/assets/images/tooltip-arrow.svg deleted file mode 100644 index a6bda5162..000000000 --- a/src/apps/earn/src/assets/images/tooltip-arrow.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/assets/images/tooltip-info.svg b/src/apps/earn/src/assets/images/tooltip-info.svg deleted file mode 100644 index bb9b227af..000000000 --- a/src/apps/earn/src/assets/images/tooltip-info.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/assets/images/update-success-big.svg b/src/apps/earn/src/assets/images/update-success-big.svg deleted file mode 100644 index ba202e104..000000000 --- a/src/apps/earn/src/assets/images/update-success-big.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/src/apps/earn/src/assets/images/upload-fail.svg b/src/apps/earn/src/assets/images/upload-fail.svg deleted file mode 100644 index e402a5bb4..000000000 --- a/src/apps/earn/src/assets/images/upload-fail.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/assets/images/upload-loading.svg b/src/apps/earn/src/assets/images/upload-loading.svg deleted file mode 100644 index f96a787eb..000000000 --- a/src/apps/earn/src/assets/images/upload-loading.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/apps/earn/src/assets/images/upload-success.svg b/src/apps/earn/src/assets/images/upload-success.svg deleted file mode 100644 index 088cb2148..000000000 --- a/src/apps/earn/src/assets/images/upload-success.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/components/AccessDenied/index.jsx b/src/apps/earn/src/components/AccessDenied/index.jsx deleted file mode 100644 index 103530bd2..000000000 --- a/src/apps/earn/src/components/AccessDenied/index.jsx +++ /dev/null @@ -1,69 +0,0 @@ -import PT from "prop-types"; -import _ from "lodash"; - -import { EnvironmentConfig } from "~/config"; -import { LinkButton } from "~/libs/ui"; -import { ReactComponent as TopcoderLogo } from "@earn/assets/icons/logo_topcoder.svg"; - -import { ACCESS_DENIED_REASON } from "../../constants"; -import { styled as styledCss } from "../../utils"; - -import styles from "./styles.module.scss"; -const styled = styledCss(styles) - -const AccessDenied = ({ cause, redirectLink, children }) => { - const retUrl = encodeURIComponent(window.location.href); - switch (cause) { - case ACCESS_DENIED_REASON.NOT_AUTHENTICATED: { - return ( -
- -
- You must be authenticated to access this page. -
-
- - Log In Here - -
-
- ); - } - case ACCESS_DENIED_REASON.NOT_AUTHORIZED: - return ( -
- -
You are not authorized to access this page.
- {children} -
- ); - case ACCESS_DENIED_REASON.HAVE_NOT_SUBMITTED_TO_THE_CHALLENGE: - return ( -
- -
You have not submitted to this challenge
- Back to the challenge -
- ); - default: - return
; - } -}; - -AccessDenied.defaultProps = { - cause: ACCESS_DENIED_REASON.NOT_AUTHENTICATED, - redirectLink: "", - children: null, -}; - -AccessDenied.propTypes = { - cause: PT.oneOf(_.toArray(ACCESS_DENIED_REASON)), - redirectLink: PT.string, - children: PT.node, -}; - -export default AccessDenied; diff --git a/src/apps/earn/src/components/AccessDenied/styles.module.scss b/src/apps/earn/src/components/AccessDenied/styles.module.scss deleted file mode 100644 index 41e351b8f..000000000 --- a/src/apps/earn/src/components/AccessDenied/styles.module.scss +++ /dev/null @@ -1,48 +0,0 @@ -@import '@earn/styles/variables'; -@import '@earn/styles/mixins'; - -.access-denied { - @include tc-body-lg; - - padding-top: 100px; - text-align: center; - width: 100%; -} - -.msg { - padding-top: 48px; - - a { - cursor: pointer; - } -} - -.joinNow { - &, - &:hover, - &:visited { - color: $tc-light-blue; - text-decoration: underline; - } -} - -.policy { - font-weight: bold; - margin-right: 24px; - - &, - &:hover, - &:visited { - color: $tc-light-blue; - text-decoration: underline; - } -} - -.copyright { - @include roboto-regular; - - color: $tc-gray-60; - font-size: 12pt; - margin-top: 128px; - text-transform: uppercase; -} diff --git a/src/apps/earn/src/components/Avatar/index.jsx b/src/apps/earn/src/components/Avatar/index.jsx deleted file mode 100644 index 9e74f6467..000000000 --- a/src/apps/earn/src/components/Avatar/index.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import { ReactComponent as DefaultUserAvatar } from '@earn/assets/images/default-user-avatar.svg'; -import React from 'react'; -import { themr } from 'react-css-super-themr'; -import ProtoAvatar from '../ProtoAvatar'; - -import theme from './style.scss'; - -function Avatar(props) { - return ; -} - -export default themr('Avatar', theme)(Avatar); \ No newline at end of file diff --git a/src/apps/earn/src/components/Avatar/style.scss b/src/apps/earn/src/components/Avatar/style.scss deleted file mode 100644 index 843c97d37..000000000 --- a/src/apps/earn/src/components/Avatar/style.scss +++ /dev/null @@ -1,5 +0,0 @@ -.avatar { - border-radius: 16px; - height: 32px; - width: 32px; - } \ No newline at end of file diff --git a/src/apps/earn/src/components/Banner/index.jsx b/src/apps/earn/src/components/Banner/index.jsx deleted file mode 100644 index 96ac9b552..000000000 --- a/src/apps/earn/src/components/Banner/index.jsx +++ /dev/null @@ -1,48 +0,0 @@ -import { useState } from "react"; -import { ReactComponent as BannerChevronUp} from "../../assets/icons/banner-chevron-up.svg"; -import classNames from 'classnames' - -import styles from "./styles.scss"; - -export const Banner = () => { - const [isExpanded, setIsExpanded] = useState(false); - const header = - "Welcome to our BETA work listings site - Tell us what you think!"; - - return ( -
-

- {header} - - setIsExpanded(!isExpanded)} - > - - -

- - {isExpanded && ( -
-

- Welcome to the Beta version of the new Challenge Listings. During - this Beta phase, we will be fine-tuning the platform based on - feedback we receive from you, our community members. -

-

NOTE THAT THIS IS NOT THE FINAL VERSION OF THE SITE.

-

- You may encounter occasional broken links or error messages. If so, - please let us know! This is what the Beta phase is intended for, and - your feedback will enable us to greatly improve the new site.{" "} -

-

You can click on the Feedback button on page.

-

Thank you!

-
- )} -
- ); -}; - -export default Banner; diff --git a/src/apps/earn/src/components/Banner/styles.scss b/src/apps/earn/src/components/Banner/styles.scss deleted file mode 100644 index 45711f32c..000000000 --- a/src/apps/earn/src/components/Banner/styles.scss +++ /dev/null @@ -1,65 +0,0 @@ -@import "@earn/styles/variables"; -@import "@earn/styles/mixins"; - -.banner { - display: flex; - justify-content: flex-start; - flex-wrap: wrap; - - width: 100%; - background: linear-gradient(90deg, $tc-blue 0%, $tc-turquoise 100%); - border-radius: 10px; - margin-bottom: 22px; - color: $tc-white; - padding-left: 28px; - - .header { - display: flex; - width: 100%; - justify-content: space-between; - flex-direction: row; - align-items: center; - @include roboto-bold; - line-height: 25px; - min-height: 50px; - font-size: 20px; - text-transform: uppercase; - } - - .chevron { - margin-right: 20px; - margin-top: 5px; - - &.expanded { - transform: rotate(180deg); - } - - &:hover { - cursor: pointer; - } - } - - .content { - display: flex; - flex-direction: column; - justify-content: flex-start; - font-size: 16px; - margin-bottom: 24px; - width: 85%; - - h3 { - font-size: 15px; - font-weight: bold; - margin-top: 15px; - } - - p { - margin-top: 15px; - } - - a { - text-decoration: underline; - } - - } -} diff --git a/src/apps/earn/src/components/Checkbox/index.jsx b/src/apps/earn/src/components/Checkbox/index.jsx deleted file mode 100644 index 6325007b5..000000000 --- a/src/apps/earn/src/components/Checkbox/index.jsx +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Checkbox component. - */ -import React, { useRef, useState, useEffect } from "react"; -import PT from "prop-types"; -import _ from "lodash"; - -import config from "../../config"; -import iconCheckL from "../../assets/icons/checkmark-large.png"; -import iconCheckM from "../../assets/icons/checkmark-medium.png"; -import iconCheckS from "../../assets/icons/checkmark-small.png"; - -import styles from "./styles.scss"; -import { styled as styledCss } from "@earn/utils"; -const styled = styledCss(styles) - -function Checkbox({ checked, onChange, size, errorMsg }) { - const [checkedInternal, setCheckedInternal] = useState(checked); - let sizeStyle = size === "lg" ? "lgSize" : null; - const imgSrc = - size === "xs" ? iconCheckS : size === "sm" ? iconCheckM : iconCheckL; - if (!sizeStyle) { - sizeStyle = size === "xs" ? "xsSize" : "smSize"; - } - const delayedOnChange = useRef( - _.debounce((q, cb) => cb(q), config.GUIKIT.DEBOUNCE_ON_CHANGE_TIME) // eslint-disable-line no-undef - ).current; - - useEffect(() => { - setCheckedInternal(checked); - }, [checked]); - - return ( - - ); -} - -Checkbox.defaultProps = { - checked: false, - onChange: () => {}, - size: "sm", - errorMsg: "", -}; - -Checkbox.propTypes = { - checked: PT.bool, - onChange: PT.func, - size: PT.oneOf(["xs", "sm", "lg"]), - errorMsg: PT.string, -}; - -export default Checkbox; diff --git a/src/apps/earn/src/components/Checkbox/styles.scss b/src/apps/earn/src/components/Checkbox/styles.scss deleted file mode 100644 index 1f78c4dcf..000000000 --- a/src/apps/earn/src/components/Checkbox/styles.scss +++ /dev/null @@ -1,117 +0,0 @@ -@import "@earn/styles/variables"; -@import "@earn/styles/GUIKit/default"; - -/* Create a custom checkbox */ -.checkmark { - position: absolute; - top: 0; - left: 0; - background-color: $tc-white; - border: 1px solid $gui-kit-gray-30; - - &.haveError { - border: 2px solid #ef476f; - } - - /* Create the checkmark/indicator (hidden when not checked) */ - .after { - position: absolute; - display: none; - left: 50%; - top: 50%; - } -} - -/* The container */ -.container { - display: block; - position: relative; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - - // lg size - &.lgSize { - width: 25px; - height: 25px; - - .checkmark { - width: 25px; - height: 25px; - border-radius: 4px; - - .after { - margin-left: -9px; - margin-top: -7px; - } - } - } - - // sm size - &.smSize { - width: 20px; - height: 20px; - - .checkmark { - width: 20px; - height: 20px; - border-radius: 3px; - - .after { - margin-left: -7px; - margin-top: -6px; - } - } - } - - // xs size - &.xsSize { - width: 15px; - height: 15px; - - .checkmark { - width: 15px; - height: 15px; - border-radius: 2px; - - .after { - margin-left: -6px; - margin-top: -4px; - } - } - } - - /* Hide the browser's default checkbox */ - input { - position: absolute; - opacity: 0; - cursor: pointer; - height: 0; - width: 0; - - /* When the checkbox is checked, add a blue background */ - &:checked ~ .checkmark { - background-color: $gui-kit-level-2; - border: none; - box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.29); - - /* Show the checkmark when checked */ - .after { - display: block; - } - } - } -} - -.errorMessage { - display: block; - - @include errorMessage; - - position: absolute; - white-space: nowrap; - margin: 35px 0 0 0; - color: #ef476f; -} diff --git a/src/apps/earn/src/components/CurrencyField/index.jsx b/src/apps/earn/src/components/CurrencyField/index.jsx deleted file mode 100644 index f5779ec3c..000000000 --- a/src/apps/earn/src/components/CurrencyField/index.jsx +++ /dev/null @@ -1,152 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; -import PT from "prop-types"; -import debounce from "lodash/debounce"; -import { - convertNumberStringToNumber, - integerFormatter, - isValidNumberString, -} from "../../utils/gigs/formatting"; -import { DEBOUNCE_ON_CHANGE_TIME } from "../../constants/index.js"; -import { PAYMENT_MAX_VALUE } from "../../constants/gigs"; -import { isNumber } from "lodash"; - -import styles from "./styles.scss"; - -/** - * Displays currency input field. - * - * @param {Object} props component properties - * @param {string} [props.className] class name added to root element - * @param {string} props.currency currency abbreviation - * @param {string} props.id id for input element - * @param {string} props.label field label - * @param {number} [props.maxValue] maximum value - * @param {number} [props.minValue] minimum value - * @param {string} props.name name for input element - * @param {(v: string) => void} props.onChange function called when input value changes - * @param {(v: number) => void} props.onCommit function called after some delay - * when input value changes and it is valid - * @param {boolean} [props.required] whether the field required non-empty value - * @param {*} props.value input value - * @returns {JSX.Element} - */ -const CurrencyField = ({ - className, - currency, - id, - label, - maxValue, - minValue, - name, - rangeError, - onError, - onChange, - onCommit, - required = false, - value, -}) => { - const [error, setError] = useState(""); - - // eslint-disable-next-line react-hooks/exhaustive-deps - const checkValue = useCallback( - debounce( - (value) => { - if (!value) { - if (required) { - setError("Required"); - } - return; - } - let num = convertNumberStringToNumber(value); - let min = isNumber(minValue) - ? minValue - : convertNumberStringToNumber(minValue); - let max = isNumber(maxValue) - ? maxValue - : convertNumberStringToNumber(maxValue); - if (isValidNumberString(value) && !isNaN(num)) { - setError(""); - if (num < min || num > max) { - onError("Please put in a valid range."); - } else { - onError(""); - onCommit(num); - } - } else { - onError(""); - setError("Invalid format"); - } - }, - DEBOUNCE_ON_CHANGE_TIME, - { leading: false } - ), - [maxValue, minValue, onCommit, required] - ); - - const onChangeValue = useCallback( - (event) => { - let value = event.target.value; - setError(""); - if (value && isValidNumberString(value)) { - let num = convertNumberStringToNumber(value); - if (!isNaN(num)) { - value = integerFormatter.format(num); - } - } - onChange(value); - checkValue(value); - }, - [checkValue, onChange] - ); - - useEffect(() => { - checkValue(value); - }, [checkValue, value]); - - return ( -
- {label && ( - - )} -
- - {currency} - {error && {error}} -
-
- ); -}; - -CurrencyField.defaultProps = { - maxValue: PAYMENT_MAX_VALUE, - minValue: 0, -}; - -CurrencyField.propTypes = { - className: PT.string, - currency: PT.string.isRequired, - id: PT.string.isRequired, - label: PT.string, - rangeError: PT.string, - maxValue: PT.oneOfType([PT.number, PT.string]), - minValue: PT.oneOfType([PT.number, PT.string]), - name: PT.string.isRequired, - onError: PT.func.isRequired, - onChange: PT.func.isRequired, - onCommit: PT.func.isRequired, - required: PT.bool, - value: PT.oneOfType([PT.number, PT.string]).isRequired, -}; - -export default CurrencyField; diff --git a/src/apps/earn/src/components/CurrencyField/styles.scss b/src/apps/earn/src/components/CurrencyField/styles.scss deleted file mode 100644 index fed55f9d9..000000000 --- a/src/apps/earn/src/components/CurrencyField/styles.scss +++ /dev/null @@ -1,70 +0,0 @@ -@import '../../styles/variables'; -@import '../../styles/mixins'; - -.container { - position: relative; -} - -.label { - z-index: 2; - position: absolute; - left: 9px; - top: 0; - padding: 0 7px; - font-size: 12px; - line-height: 12px; - transform: translateY(-50%); - color: $control-border-color; - background-color: #fff; -} - -.field { - z-index: 1; - position: relative; - display: flex; - align-items: center; - border: 1px solid $control-border-color; - border-radius: $control-border-radius; - height: 40px; - background-color: #fff; - - &.has-errors { - border-color: #EF476F; - } -} - -input.input { - flex: 1 1 0; - margin: 0; - border: none !important; - padding: 8px 0 8px 15px; - height: 22px; - font-size: 14px; - line-height: 22px; - background: none; - outline: none !important; - box-shadow: none !important; - width: 60px; -} - -.currency { - flex: 0 0 auto; - margin: 0 10px 0 6px; - font-size: 13px; - line-height: 22px; - text-transform: uppercase; - color: $control-border-color; -} - -.error { - display: block; - position: absolute; - left: 15px; - top: 100%; - margin-top: 2px; - font-size: 12px; - line-height: 17px; - white-space: nowrap; - color: $body-color; - background-color: #fff; -} diff --git a/src/apps/earn/src/components/DateRangePicker/DateInput/index.jsx b/src/apps/earn/src/components/DateRangePicker/DateInput/index.jsx deleted file mode 100644 index 881715340..000000000 --- a/src/apps/earn/src/components/DateRangePicker/DateInput/index.jsx +++ /dev/null @@ -1,167 +0,0 @@ -import React, { useRef, useEffect, useState } from "react"; -import PT from "prop-types"; -import _ from "lodash"; -import TextInput from "../../TextInput"; -import { ReactComponent as CalendarIcon} from "../../../assets/icons/icon-calendar.svg"; - -import styles from "./styles.scss"; -import { styled as styledCss } from "../../../utils"; -const styled = styledCss(styles) - -const DateInput = ({ - id, - isStartDateActive, - startDateString, - onStartDateChange, - onStartDateFocus, - isEndDateActive, - endDateString, - onEndDateChange, - onEndDateFocus, - error, - onClickCalendarIcon, - onStartEndDateChange, - placeholder, - enterToSubmit, -}) => { - const ref = useRef(null); - const [focused, setFocused] = useState(false); - - let rangeText; - if (startDateString && endDateString) { - rangeText = `${startDateString} - ${endDateString}`; - } else { - rangeText = `${startDateString}${endDateString}`; - } - - useEffect(() => { - const inputElement = ref.current.querySelector("input"); - const onFocus = () => setFocused(true); - const onBlur = () => setFocused(false); - - inputElement.addEventListener("focus", onFocus); - inputElement.addEventListener("blur", onBlur); - - return () => { - inputElement.removeEventListener("focus", onFocus); - inputElement.removeEventListener("blur", onBlur); - }; - }, []); - - const latestPropsRef = useRef(null); - latestPropsRef.current = { onStartDateFocus, onEndDateFocus }; - - useEffect(() => { - const inputElement = ref.current.querySelector("input"); - - let caretPosition; - if (inputElement.selectionDirection === "forward") { - caretPosition = inputElement.selectionEnd; - } else { - caretPosition = inputElement.selectionStart; - } - - if (caretPosition < 14) { - latestPropsRef.current.onStartDateFocus(); - } else { - latestPropsRef.current.onEndDateFocus(); - } - }, [focused]); - - const onChangeRangeText = (value) => { - let [newStartDateString = "", newEndDateString = ""] = value - .trim() - .split("-"); - newStartDateString = newStartDateString.trim(); - newEndDateString = newEndDateString.trim(); - - if ( - newStartDateString !== startDateString && - newEndDateString !== endDateString - ) { - const event = { - startDateString: newStartDateString, - endDateString: newEndDateString, - }; - onStartEndDateChange(event); - onStartDateFocus(); - } else if (newStartDateString !== startDateString) { - onStartDateFocus(); - onStartDateChange(newStartDateString); - } else if (newEndDateString !== endDateString) { - onEndDateFocus(); - onEndDateChange(newEndDateString); - if (newEndDateString === "") { - onStartDateFocus(); - } - } - }; - - const onChangeRangeTextDebounced = useRef(_.debounce((f) => f(), 1000)); - - const onClickIcon = (event) => { - event.stopPropagation(); - - const inputElement = ref.current.querySelector("input"); - - let caretPosition; - if (inputElement.selectionDirection === "forward") { - caretPosition = inputElement.selectionEnd; - } else { - caretPosition = inputElement.selectionStart; - } - - if (caretPosition < 14 || caretPosition === rangeText.length) { - onClickCalendarIcon("start"); - } else { - onClickCalendarIcon("end"); - } - }; - - const label = startDateString ? "From" : endDateString ? "To" : "From"; - - return ( -
-
- { - if (!enterToSubmit) { - onChangeRangeTextDebounced.current(() => - onChangeRangeText(value) - ); - } - }} - onEnterKey={(value) => { - onChangeRangeText(value); - }} - placeholder={placeholder} - /> - -
-
{error}
-
- ); -}; - -DateInput.propTypes = { - id: PT.string, - isStartDateActive: PT.bool, - startDateString: PT.string, - onStartDateChange: PT.func, - onStartDateFocus: PT.func, - isEndDateActive: PT.bool, - endDateString: PT.string, - onEndDateChange: PT.func, - onEndDateFocus: PT.func, - error: PT.string, - onClickCalendarIcon: PT.func, - onStartEndDateChange: PT.func, - placeholder: PT.string, -}; - -export default DateInput; diff --git a/src/apps/earn/src/components/DateRangePicker/DateInput/styles.scss b/src/apps/earn/src/components/DateRangePicker/DateInput/styles.scss deleted file mode 100644 index 8b637437b..000000000 --- a/src/apps/earn/src/components/DateRangePicker/DateInput/styles.scss +++ /dev/null @@ -1,49 +0,0 @@ -@import "@earn/styles/variables"; -@import "@earn/styles/mixins"; - -.container { - &.isError { - input { - border: 1px solid $tc-level-5; - } - - .errorHint { - display: block; - color: $tc-level-5; - font-size: 12px; - padding: 4px 0; - height: 20px; - } - } -} - -.date-range-input { - margin-top: -12px; - font-size: $font-size-sm; - - @include textinput-show-label; -} - -.input-group { - position: relative; - - .icon { - position: absolute; - top: 22px; - right: 14px; - display: block; - cursor: pointer; - appearance: none; - background: none; - border: none; - padding: 0; - } - - input { - padding-right: 46px !important; - } -} - -.errorHint { - display: none; -} diff --git a/src/apps/earn/src/components/DateRangePicker/helpers.js b/src/apps/earn/src/components/DateRangePicker/helpers.js deleted file mode 100644 index df745c982..000000000 --- a/src/apps/earn/src/components/DateRangePicker/helpers.js +++ /dev/null @@ -1,136 +0,0 @@ -import { useEffect, useRef, useState } from "react"; -import moment from "moment"; - -/** - * Check whether the dates are same - * @param {Date} date1. - * @param {Date} date2. - * @return {boolean} - */ -export function isSameDay(date1, date2) { - if (!date1 || !date2) return false; - return moment(date1).isSame(moment(date2), "day"); -} - -/** - * Check whether the date1 is occur before date2 - * @param {Date} date1. - * @param {Date} date2. - * @return {boolean} - */ -export function isBeforeDay(date1, date2) { - if (!date1 || !date2) return false; - return moment(date1).isBefore(moment(date2), "day"); -} - -/** - * Check whether the date1 is occur after date2 - * @param {Date} date1. - * @param {Date} date2. - * @return {boolean} - */ -export function isAfterDay(date1, date2) { - if (!date1 || !date2) return false; - return moment(date1).isAfter(moment(date2), "day"); -} - -const staticRangeHandler = { - range: {}, - isSelected(range) { - const definedRange = this.range(); - return ( - isSameDay(range.startDate, definedRange.startDate) && - isSameDay(range.endDate, definedRange.endDate) - ); - }, -}; - -/** - * Create defined date ranges - * @return {object[]} list of defined ranges - */ -export function createStaticRanges() { - const today = moment(); - const endOfToday = today.set({ - hour: 23, - minute: 59, - second: 59, - millisecond: 999, - }); - const pastWeek = endOfToday.clone().subtract(1, "week"); - const pastMonth = endOfToday.clone().subtract(1, "month"); - const past6Months = endOfToday.clone().subtract(6, "month"); - const pastYear = endOfToday.clone().subtract(1, "year"); - - const ranges = [ - { - label: "Past Week", - range: () => ({ - startDate: pastWeek.startOf("day").toDate(), - endDate: endOfToday.toDate(), - }), - }, - { - label: "Past Month", - range: () => ({ - startDate: pastMonth.startOf("day").toDate(), - endDate: endOfToday.toDate(), - }), - }, - { - label: "Past 6 Months", - range: () => ({ - startDate: past6Months.startOf("day").toDate(), - endDate: endOfToday.toDate(), - }), - }, - { - label: "Past Year", - range: () => ({ - startDate: pastYear.startOf("day").toDate(), - endDate: endOfToday.toDate(), - }), - }, - ]; - - return ranges.map((range) => ({ ...staticRangeHandler, ...range })); -} - -/** - * React hook for checking if the click is from outside the reference - * @param {boolean} initialIsVisible true if visible and false if hidden - */ -export function useComponentVisible(initialIsVisible) { - const [isComponentVisible, setIsComponentVisible] = useState( - initialIsVisible - ); - const ref = useRef(null); - - const handleHideDropdown = (event) => { - if (event.key === "Escape") { - setIsComponentVisible(false); - } - }; - - const handleClickOutside = (event) => { - if (ref.current && !ref.current.contains(event.target)) { - setIsComponentVisible(false); - } - }; - - useEffect(() => { - document.addEventListener("keydown", handleHideDropdown, true); - document.addEventListener("click", handleClickOutside, true); - return () => { - document.removeEventListener("keydown", handleHideDropdown, true); - document.removeEventListener("click", handleClickOutside, true); - }; - }); - - return { ref, isComponentVisible, setIsComponentVisible }; -} - -export default { - useComponentVisible, - createStaticRanges, -}; diff --git a/src/apps/earn/src/components/DateRangePicker/index.jsx b/src/apps/earn/src/components/DateRangePicker/index.jsx deleted file mode 100644 index fbd840320..000000000 --- a/src/apps/earn/src/components/DateRangePicker/index.jsx +++ /dev/null @@ -1,651 +0,0 @@ -import React, { useState, useEffect } from "react"; -import moment from "moment"; -import { DateRangePicker as ReactDateRangePicker } from "react-date-range"; -import PropTypes from "prop-types"; - -import styles from "./style.scss"; -import { styled as styledCss } from "@earn/utils"; - -import DateInput from "./DateInput"; - -import { - useComponentVisible, - createStaticRanges, - isSameDay, - isAfterDay, - isBeforeDay, -} from "./helpers"; - -const styled = styledCss(styles); - -function DateRangePicker(props) { - const { id, range, onChange, placeholder, enterToSubmit = false } = props; - - const [rangeString, setRangeString] = useState({ - startDateString: "", - endDateString: "", - }); - const [activeDate, setActiveDate] = useState(null); - const [preview, setPreview] = useState(null); - const [focusedRange, setFocusedRange] = useState([0, 0]); - const [errors, setErrors] = useState({ - startDate: undefined, - endDate: undefined, - }); - - const { - ref: calendarRef, - isComponentVisible, - setIsComponentVisible, - } = useComponentVisible(false); - - const isStartDateFocused = focusedRange[1] === 0; - const isEndDateFocused = focusedRange[1] === 1; - - useEffect(() => { - setRangeString({ - startDateString: range.startDate - ? moment(range.startDate).format("MMM D, YYYY") - : "", - endDateString: range.endDate - ? moment(range.endDate).format("MMM D, YYYY") - : "", - }); - }, [range]); - - /** - * Handle end date change on user input - * After user input the end date via keyboard, validate it then update the range state - * @param {Object} e Input Event. - */ - const onEndDateChange = (value) => { - const endDateString = value; - const endDate = moment(endDateString, "MMM D, YYYY", true); - const startDate = moment(rangeString.startDateString, "MMM D, YYYY", true); - - if (endDate.isValid() && isBeforeDay(endDate, startDate)) { - setErrors({ - ...errors, - endDate: "Range Error", - }); - } else if (endDate.isValid()) { - onChange({ - endDate: endDate.toDate(), - startDate: range.startDate, - }); - - setErrors({ - ...errors, - endDate: "", - }); - - setRangeString({ - ...rangeString, - endDateString: endDate.format("MMM D, YYYY"), - }); - } else if (endDateString === "") { - onChange({ - endDate: null, - startDate: range.startDate, - }); - - setErrors({ - ...errors, - endDate: "", - }); - } else { - setErrors({ - ...errors, - endDate: "Invalid End Date Format", - }); - - setRangeString({ - ...rangeString, - endDateString, - }); - } - }; - - /** - * Handle start date change on user input - * After user input the start date via keyboard, validate it then update the range state - * @param {Object} e Input Event. - */ - const onStartDateChange = (value) => { - const startDateString = value; - const startDate = moment(startDateString, "MMM D, YYYY", true); - const endDate = moment(rangeString.endDateString, "MMM D, YYYY", true); - - if ( - startDate.isValid() && - endDate.isValid() && - isAfterDay(startDate, endDate) - ) { - setErrors({ - ...errors, - startDate: "Range Error", - }); - } else if (startDate.isValid()) { - onChange({ - endDate: range.endDate, - startDate: startDate.toDate(), - }); - - setErrors({ - ...errors, - startDate: "", - }); - - setRangeString({ - ...rangeString, - startDateString: startDate.format("MMM D, YYYY"), - }); - } else if (startDateString === "") { - onChange({ - endDate: range.endDate, - startDate: null, - }); - - setErrors({ - ...errors, - startDate: "", - }); - } else { - setErrors({ - ...errors, - startDate: "Invalid Start Date Format", - }); - - setRangeString({ - ...rangeString, - startDateString, - }); - } - }; - - const onStartEndDateChange = ({ startDateString, endDateString }) => { - const startDate = moment(startDateString, "MMM D, YYYY", true); - const endDate = moment(endDateString, "MMM D, YYYY", true); - - if ( - startDate.isValid() && - endDate.isValid() && - isBeforeDay(endDate, startDate) - ) { - setErrors({ - ...errors, - endDate: "Range Error", - }); - } else if (startDate.isValid() && endDate.isValid()) { - onChange({ - endDate: endDate.toDate(), - startDate: startDate.toDate(), - }); - setErrors({ - startDate: "", - endDate: "", - }); - } else if (startDate.isValid()) { - onChange({ - endDate: null, - startDate: startDate.toDate(), - }); - setErrors({ - ...errors, - endDate: "Invalid End Date Format", - }); - } else if (endDate.isValid()) { - onChange({ - endDate: endDate.toDate(), - startDate: null, - }); - setErrors({ - ...errors, - startDate: "Invalid Start Date Format", - }); - } else if (startDateString === "" && endDateString === "") { - onChange({ - endDate: null, - startDate: null, - }); - setErrors({ - startDate: "", - endDate: "", - }); - } else if (startDateString === "") { - onChange({ - endDate: endDate.toDate(), - startDate: null, - }); - - setErrors({ - ...errors, - startDate: "", - }); - } else if (endDateString === "") { - onChange({ - endDate: null, - startDate: startDate.toDate(), - }); - - setErrors({ - ...errors, - endDate: "", - }); - } else { - onChange({ - endDate: null, - startDate: null, - }); - setErrors({ - startDate: "Invalid Start Date Format", - endDate: "Invalid End Date Format", - }); - } - }; - - /** - * Trigger to open calendar modal on calendar icon in start date input - */ - const onIconClickStartDate = () => { - const calendarIcon = document.querySelector(id); - if (calendarIcon) { - calendarIcon.blur(); - } - setFocusedRange([0, 0]); // set current focused input to start date - setActiveDate(null); - setIsComponentVisible(true); - setPreview(null); - }; - - /** - * Trigger to open calendar modal on calendar icon in end date input - */ - const onIconClickEndDate = () => { - const calendarIcon = document.querySelector(id); - if (calendarIcon) { - calendarIcon.blur(); - } - setFocusedRange([0, 1]); // set current focused input to end date - setActiveDate(null); - setIsComponentVisible(true); - setPreview(null); - }; - - const onReset = (presetRange) => { - let newStartDate; - let newEndDate; - - if (presetRange) { - newStartDate = presetRange.startDate; - newEndDate = presetRange.endDate; - } - - setFocusedRange([0, 0]); - - setErrors({ - startDate: "", - endDate: "", - }); - - setRangeString({ - startDateString: newStartDate - ? moment(newStartDate).format("MMM D, YYYY") - : "", - endDateString: newEndDate ? moment(newEndDate).format("MMM D, YYYY") : "", - }); - - onChange({ - startDate: newStartDate ? moment(newStartDate) : null, - endDate: newEndDate ? moment(newEndDate) : null, - }); - - setIsComponentVisible(false); - }; - - /** - * Event handler on date selection changes - * @param {Object} newRange nnew range that has endDate and startDate data - */ - const onDateRangePickerChange = (newRange) => { - let newEndDate = newRange.endDate; - let newStartDate = newRange.startDate; - const isUseKeyPress = focusedRange[0] !== 0; - - if (isUseKeyPress) { - setFocusedRange([0, focusedRange[1]]); - } - - let shouldCloseCalendar = true; - let shouldOpenNextCalendar = false; - - // User is active on start date calendar modal - if ( - isStartDateFocused && - (isUseKeyPress || isSameDay(newStartDate, newEndDate)) - ) { - if (range.endDate && isAfterDay(newStartDate, range.endDate)) return; - newEndDate = range.endDate; - shouldCloseCalendar = false; - shouldOpenNextCalendar = true; - setErrors({ - ...errors, - startDate: "", - }); - } else if ( - isEndDateFocused && - (isUseKeyPress || isSameDay(newEndDate, newStartDate)) - ) { - if (range.startDate && isBeforeDay(newEndDate, range.startDate)) return; - newStartDate = range.startDate; - setErrors({ - ...errors, - endDate: "", - }); - } else { - setErrors({ - startDate: "", - endDate: "", - }); - } - - // Emit the payload - - setRangeString({ - startDateString: newStartDate - ? moment(newStartDate).format("MMM D, YYYY") - : "", - endDateString: newEndDate ? moment(newEndDate).format("MMM D, YYYY") : "", - }); - - onChange({ - startDate: newStartDate, - endDate: newEndDate ? moment(newEndDate).endOf("day").toDate() : null, - }); - - if (shouldOpenNextCalendar) { - setFocusedRange([0, 1]); - } - if (shouldCloseCalendar) { - setIsComponentVisible(false); - } - }; - - /** - * Event handler on preview change - * @param {Date} date current date which user hover - */ - const onPreviewChange = (date) => { - if (!(date instanceof Date)) { - setPreview(null); - - // --- - // workaround for fixing issue 132: - // - set the active range's background to transparent color - // to prevent the calendar auto focusing on the day of today by default when no - // start date nor end date are set. - // - does not set focus on the selection range when mouse leaves. - // --- - - // setActiveDate(null); - // if (range.startDate || range.endDate) { - // setFocusedRange([0, focusedRange[1]]); - // } - return; - } - - if (isStartDateFocused && date) { - setPreview({ - startDate: date, - endDate: range.endDate || date, - }); - } else if (isEndDateFocused && date) { - setPreview({ - startDate: range.startDate || date, - endDate: date, - }); - } - - setActiveDate(date); - setFocusedRange([1, focusedRange[1]]); - }; - - /** - * Event handler for user keypress - * @param {Event} e Keyboard event - */ - const handleKeyDown = (e) => { - let currentActiveDate = activeDate; - if (!currentActiveDate) { - currentActiveDate = moment().startOf("month").toDate(); - - if (isStartDateFocused && range.startDate) { - currentActiveDate = range.startDate; - } else if (isEndDateFocused && (range.startDate || range.endDate)) { - currentActiveDate = range.endDate || range.startDate; - } - } - - switch (e.key) { - case "Down": - case "ArrowDown": - currentActiveDate = moment(currentActiveDate).add(7, "days").toDate(); - onPreviewChange(currentActiveDate); - break; - case "Up": - case "ArrowUp": - currentActiveDate = moment(currentActiveDate) - .subtract(7, "days") - .toDate(); - onPreviewChange(currentActiveDate); - break; - case "Left": - case "ArrowLeft": - currentActiveDate = moment(currentActiveDate) - .subtract(1, "days") - .toDate(); - onPreviewChange(currentActiveDate); - break; - case "Right": - case "ArrowRight": - currentActiveDate = moment(currentActiveDate).add(1, "days").toDate(); - onPreviewChange(currentActiveDate); - break; - case "Enter": - if (activeDate) { - onDateRangePickerChange({ - startDate: isStartDateFocused ? activeDate : range.startDate, - endDate: isEndDateFocused ? activeDate : range.endDate, - }); - } - break; - case "Esc": - case "Escape": - setIsComponentVisible(false); - break; - default: - return; // Quit when this doesn't handle the key event. - } - - e.preventDefault(); - }; - - /** - * User Effect for listening to keypress event - */ - useEffect(() => { - if (isComponentVisible) { - document.addEventListener("keydown", handleKeyDown, true); - } else { - document.removeEventListener("keydown", handleKeyDown, true); - } - - return () => { - document.removeEventListener("keydown", handleKeyDown, true); - }; - }); - - /** - * Focus the calendar to the given date, - * so for example, if the user click menu for end date it will open the calendar - * and focus it to current end date - */ - const getShownDate = () => { - if (activeDate) { - return activeDate; - } - - if (isStartDateFocused) { - if (preview) return preview.startDate; - return range.startDate || moment().toDate(); - } - if (preview) return preview.endDate; - return range.endDate || moment().toDate(); - }; - - /** - * Disable the days that cannot be selected - */ - const disabledDay = (date) => { - if (isStartDateFocused) { - return range.endDate ? moment(date).isAfter(range.endDate, "day") : false; - } - return range.startDate - ? moment(date).isBefore(range.startDate, "day") - : false; - }; - - /** - * Get Date Ranges - */ - const getRanges = () => { - if (activeDate) { - return [ - { - ...range, - key: "selection", - color: "#0AB88A", - }, - { - startDate: activeDate, - endDate: activeDate, - key: "active", - color: preview ? "#D8FDD8" : "#D8FDD800", - }, - ]; - } - return [ - { - ...range, - key: "selection", - color: "#0AB88A", - }, - ]; - }; - - /** - * Check whether the preview invalid - */ - const isInvalidPreview = () => { - if (!preview) return false; - if (isStartDateFocused) { - return isAfterDay(preview.startDate, range.endDate); - } - return isBeforeDay(preview.endDate, range.startDate); - }; - - const className = `${(focusedRange[1] === 1 && styles.endDate) || ""} ${ - (range.startDate && range.endDate && styles.isRange) || "" - } ${(isInvalidPreview() && styles.isInvalidPreview) || ""} ${ - ((errors.startDate || errors.endDate) && styles.isErrorInput) || "" - }`; - - return ( -
-
- setFocusedRange([0, 0])} - isEndDateActive={focusedRange[1] === 1 && isComponentVisible} - endDateString={rangeString.endDateString} - onEndDateChange={onEndDateChange} - onEndDateFocus={() => setFocusedRange([0, 1])} - error={errors.startDate || errors.endDate} - onClickCalendarIcon={(event) => { - event === "start" ? onIconClickStartDate() : onIconClickEndDate(); - }} - onStartEndDateChange={onStartEndDateChange} - placeholder={placeholder} - enterToSubmit={enterToSubmit} - /> -
-
- {isComponentVisible && ( -
- { - if (!preview) { - onReset(item.selection || item.active); - } else { - onDateRangePickerChange(item.selection || item.active); - } - }} - dateDisplayFormat="MM/dd/yyyy" - showDateDisplay={false} - staticRanges={createStaticRanges()} - inputRanges={[]} - moveRangeOnFirstSelection={false} - initialFocusedRange={[0, 1]} - showMonthArrow={false} - ranges={getRanges()} - disabledDay={disabledDay} - shownDate={getShownDate()} - preview={preview} - onPreviewChange={onPreviewChange} - /> -
- - -
-
- )} -
-
- ); -} - -// It use https://www.npmjs.com/package/react-date-range internally -// Check the docs for further options - -DateRangePicker.propTypes = { - id: PropTypes.string, - range: PropTypes.object.isRequired, - onChange: PropTypes.func.isRequired, - placeholder: PropTypes.string, -}; - -DateRangePicker.defaultProps = { - id: "input-date-range-calendar-icon", - placeholder: "Date range", -}; - -export default DateRangePicker; diff --git a/src/apps/earn/src/components/DateRangePicker/style.scss b/src/apps/earn/src/components/DateRangePicker/style.scss deleted file mode 100644 index b00f99541..000000000 --- a/src/apps/earn/src/components/DateRangePicker/style.scss +++ /dev/null @@ -1,463 +0,0 @@ -@import "@earn/styles/variables"; -@import "@earn/styles/mixins"; - -$green: #D8FDD8; -$darkGreen: #0AB88A;; - -@mixin phone { - @media (max-width: #{$screen-sm - 1px}) { - @content; - } -} - -@mixin tablet { - @media (min-width: #{$screen-sm}) and (max-width: #{$screen-lg - 1px}) { - @content; - } -} - -.isErrorInput { - :global .rdrDateRangePickerWrapper { - margin-top: -20px !important; - } -} - -.isRange { - :global { - .rdrDay { - .rdrInRange { - background: $green !important; - } - - .rdrStartEdge, - .rdrEndEdge { - background: $green !important; - } - } - } -} - -.isInvalidPreview { - :global { - .rdrDayInPreview, - .rdrDayStartPreview, - .rdrDayEndPreview, - .rdrStartEdge.rdrEndEdge { - border: none !important; - } - } -} - -.dateRangePicker { - display: block; - position: relative; - color: $tc-black; - - :global { - @include phone { - .rdrDateRangePickerWrapper { - position: relative !important; - width: 100% !important; - flex-direction: column-reverse; - align-items: center; - justify-content: flex-end; - padding: 0 20px; - border-radius: 0 !important; - - .rdrDefinedRangesWrapper { - .rdrStaticRanges { - display: inline-flex; - flex-direction: row; - justify-content: space-around; - flex-wrap: wrap; - margin-top: 10px !important; - width: calc(100% - 40px); - - .rdrStaticRange { - width: 50%; - } - - .rdrStaticRangeLabel { - font-size: 14px; - } - - > button:hover .rdrStaticRangeLabel { - background-color: $green; - } - } - } - - .rdrDateRangeWrapper { - width: 100%; - - .rdrMonthAndYearWrapper { - padding-top: 0; - - .rdrMonthAndYearPickers select { - font-size: 16px; - } - } - - .rdrMonth { - width: 100%; - } - - .rdrDayNumber { - font-size: 15px; - } - - .rdrDayToday .rdrDayNumber span:after { - bottom: -4px; - left: calc(50% + 2px); - } - - .rdrMonths { - .rdrWeekDay { - font-size: 15px; - margin-bottom: 10px; - } - - .rdrDay { - .rdrDayStartPreview { - left: 17%; - } - - .rdrDayEndPreview { - right: 17%; - } - } - } - } - } - } - - .rdrDateRangePickerWrapper { - z-index: 15; - position: relative; - // background: $tc-white; - border-bottom-left-radius: 4px; - border-bottom-right-radius: 4px; - overflow: hidden; - width: 455px; - - .rdrDefinedRangesWrapper { - width: 100%; - border: none; - - .rdrStaticRanges { - margin-top: 60px; - - .rdrStaticRange { - border: none; - } - - button { - color: $tc-gray-80; - - @include roboto-regular; - } - - button:hover .rdrStaticRangeLabel { - background-color: $green; - } - } - - .rdrInputRanges { - display: none; - } - } - } - - .rdrMonthAndYearPickers select { - background: url("data:image/svg+xml;utf8,") no-repeat right 8px center; - - option { - background: $white; - } - - &:hover { - background-color: transparent; - } - } - - .rdrMonths { - border-top: 1px solid $tc-gray-20; - border-bottom: 1px solid $tc-gray-20; - - .rdrWeekDay { - font-weight: bold; - color: #484848; - } - - .rdrDayDisabled { - background: transparent; - - .rdrDayInPreview, - .rdrDayStartPreview, - .rdrDayEndPreview, - .rdrStartEdge.rdrEndEdge { - filter: none !important; - -webkit-filter: none !important; - } - } - - .rdrDayPassive { - .rdrInRange, - .rdrStartEdge, - .rdrEndEdge, - .rdrSelected, - .rdrDayStartPreview, - .rdrDayInPreview, - .rdrDayEndPreview { - display: block; - } - - .rdrInRange ~ .rdrDayNumber span { - opacity: 0.5; - } - } - - .rdrDayEndOfMonth .rdrInRange, - .rdrDayEndOfMonth .rdrStartEdge, - .rdrDayEndOfWeek .rdrInRange, - .rdrDayEndOfWeek .rdrStartEdge { - right: 0; - } - - .rdrDayStartOfMonth .rdrInRange, - .rdrDayStartOfMonth .rdrEndEdge, - .rdrDayStartOfWeek .rdrInRange, - .rdrDayStartOfWeek .rdrEndEdge { - left: 0; - } - - .rdrDayStartOfWeek { - .rdrDayInPreview { - left: 0; - border-radius: 0 !important; - border-left: none; - } - } - - .rdrDayEndOfWeek { - .rdrDayInPreview { - right: 0; - border-radius: 0 !important; - border-right: none; - } - } - - .rdrDayStartOfMonth, - .rdrDayEndOfMonth { - .rdrDayInPreview { - border-radius: 0; - border-left: 0; - border-right: 0; - } - } - - .rdrDay { - margin-bottom: 6px; - - .rdrInRange { - color: transparent; - background: transparent; - - & ~ .rdrDayNumber span { - color: $tc-black; - } - } - - .rdrDayStartPreview { - border-radius: 18px 0 0 18px; - height: 36px; - left: 4px; - border-right: none; - } - - .rdrDayEndPreview { - border-radius: 0 18px 18px 0; - height: 36px; - right: 4px; - border-left: none; - } - - .rdrDayStartPreview.rdrDayEndPreview, - .rdrDayHovered { - border-radius: 50%; - width: 36px; - height: 36px; - left: 4px; - border: 1px solid $green; - } - - .rdrSelected, - .rdrInRange, - .rdrStartEdge, - .rdrEndEdge { - top: 0; - bottom: 0; - border-radius: 0; - } - - .rdrDayInPreview, - .rdrDayStartPreview, - .rdrDayEndPreview { - border-color: $green; - top: 0; - bottom: 0; - } - - .rdrDayStartPreview, - .rdrDayEndPreview { - & ~ .rdrDayNumber span { - color: $tc-black; - } - } - - .rdrStartEdge { - background: transparent; - left: 50%; - - &::after { - content: ""; - position: absolute; - top: 0; - left: -18px; - border-radius: 50%; - width: 36px; - height: 36px; - background: currentColor; - z-index: 0; - } - } - - .rdrEndEdge { - background: transparent; - right: 50%; - - &::after { - content: ""; - position: absolute; - top: 0; - right: -18.2px; - border-radius: 50%; - width: 36px; - height: 36px; - background: currentColor; - z-index: 0; - } - } - - .rdrStartEdge.rdrEndEdge ~ .rdrDayNumber span { - color: $tc-black; - } - - .rdrDayNumber { - top: 0; - bottom: 0; - } - } - - .rdrDayStartOfWeek, - .rdrDayEndOfWeek { - border-radius: 0; - } - - .rdrDayToday .rdrDayNumber span:after { - bottom: -4px; - left: calc(50% + 2px); - background: $darkGreen !important; - } - } - } -} - -.dateInputWrapper { - position: relative; - text-align: left; -} - -.calendar-container { - @include roboto-regular; - - position: absolute; - top: calc(100% + 8px); -} - -.calendar-inner-container { - position: absolute; - padding: 0 18px 0 0; - text-align: right; - border: 1px solid $tc-gray-30; - border-radius: 4px; - background: $tc-white; - z-index: 10; - - @include phone { - width: 100%; - position: fixed; - top: 0; - left: 0; - right: 20px; - bottom: 0; - z-index: 15; - padding: 187px 0 0; - border: 0; - border-radius: 0; - background: rgba(#2A2A2A, 0.6); - - @include down(320px) { - padding: 40px 0 0; - } - - .calendar-footer { - margin: 0 20px; - padding: 20px 0; - } - } - - .calendar-footer { - background: $tc-white; - } - - .calendar-button { - @include roboto-bold; - - width: 71px; - height: 24px; - line-height: 22px; - margin: 9px 4px 16px; - padding: 0; - font-size: 10.5px; - text-align: center; - background: transparent; - border: 1px solid #ccc; - border-radius: 2px; - - @include phone { - width: 79px; - height: 26px; - line-height: 27px; - font-size: 12px; - margin: 0 12px 0; - } - } -} - -@include tablet { - .calendar-container, - .calendar-inner-container { - right: 0; - } - - :global { - .rdrDateRangePickerWrapper { - .calendar-container, - .calendar-inner-container { - right: 62px; - left: auto; - } - } - } -} diff --git a/src/apps/earn/src/components/Dropdown/index.jsx b/src/apps/earn/src/components/Dropdown/index.jsx deleted file mode 100644 index f4b9d2d6b..000000000 --- a/src/apps/earn/src/components/Dropdown/index.jsx +++ /dev/null @@ -1,199 +0,0 @@ -/** - * Dropdown component. - */ -import React, { useState, useRef, useEffect } from "react"; -import PT from "prop-types"; -import _ from "lodash"; -import ReactSelect, { components } from "react-select"; - -import config from "../../config"; - -import styles from "./styles.scss"; -import { styled as styledCss } from "@earn/utils"; -const styled = styledCss(styles) - -const Menu = (props) => { - return ( - - {props.children} - - ); -}; -const MenuList = (props) => { - return ( - - {props.children} - - ); -}; - -const CustomOption = (props) => { - return ( - - {props.children} - - ); -}; - -const ValueContainer = ({ children, ...props }) => ( - - {children} - -); - -const Input = (props) => { - return ( - - ); -}; - -const SingleValue = ({ children, ...props }) => ( - - {children} - -); - -const ControlComponent = (props) => ( - -); -const IndicatorSeparator = () => { - return null; -}; - -function Dropdown({ - className, - options, - label, - required, - placeholder, - onChange, - errorMsg, - searchable, - size, -}) { - const [internalOptions, setInternalOptions] = useState(options); - const selectedOption = _.find(internalOptions, { selected: true }); - const [focused, setFocused] = useState(false); - const delayedOnChange = useRef( - _.debounce((q, cb) => cb(q), config.GUIKIT.DEBOUNCE_ON_CHANGE_TIME) // eslint-disable-line no-undef - ).current; - const sizeStyle = size === "lg" ? "lgSize" : "xsSize"; - useEffect(() => { - setInternalOptions(options); - }, [options]); - return ( -
setFocused(true)} - onBlurCapture={() => setFocused(false)} - className={ - styled( - className, - 'dropdownContainer', - 'container', - sizeStyle, - selectedOption && "haveValue", - errorMsg && "haveError", - focused && "isFocused" - ) - } - > -
- ({ - value: o.label, - label: o.label, - }))} - value={selectedOption} - components={{ - Menu, - MenuList, - Option: CustomOption, - ValueContainer, - SingleValue, - IndicatorSeparator, - Input, - Control: ControlComponent, - }} - onChange={(value) => { - if (value) { - const newOptions = internalOptions.map((o) => ({ - selected: value.label === o.label, - label: o.label, - value: o.value, - })); - setInternalOptions(newOptions); - delayedOnChange(_.cloneDeep(newOptions), onChange); - } - }} - placeholder={`${placeholder}${placeholder && required ? " *" : ""}`} - clearable={false} - /> -
- {label ? ( - - {label} - {required ?  * : null} - - ) : null} - {errorMsg ? ( - - {errorMsg} - - ) : null} -
- ); -} - -Dropdown.defaultProps = { - placeholder: '', - label: '', - required: false, - onChange: () => {}, - errorMsg: '', - searchable: true, - size: 'lg', -}; - -Dropdown.propTypes = { - options: PT.arrayOf( - PT.shape({ - label: PT.string, - selected: PT.bool, - }) - ).isRequired, - placeholder: PT.string, - label: PT.string, - required: PT.bool, - onChange: PT.func, - errorMsg: PT.string, - size: PT.oneOf(["xs", "lg"]), -}; - -export default Dropdown; diff --git a/src/apps/earn/src/components/Dropdown/styles.scss b/src/apps/earn/src/components/Dropdown/styles.scss deleted file mode 100644 index c844913b8..000000000 --- a/src/apps/earn/src/components/Dropdown/styles.scss +++ /dev/null @@ -1,260 +0,0 @@ -@import "@earn/styles/variables"; -@import "@earn/styles/GUIKit/default"; - -.label { - @include textInputLabel; -} - -.relative { - position: relative; -} - -.errorMessage { - @include errorMessage; -} - -.iconDropdown { - position: absolute; - top: 50%; - right: 16px; - pointer-events: none; - margin-top: -4px; -} - -.container { - position: relative; - display: flex; - flex-direction: column; - padding-top: 12px; - - &.haveValue .label, - &.haveError .label, - &.isFocused .label { - display: flex; - } - - &.isFocused { - .label { - color: $gui-kit-level-2; - } - - .iconDropdown { - transform: scale(1, -1); - } - } - - &.haveError .label, - &.haveError.isFocused .label { - color: $gui-kit-level-5; - } - - :global { - // @import '~react-select/dist/react-select'; - - width: 100%; - - .Select-control { - margin: 0; - padding: 0; - border: none; - border-radius: 6px !important; - height: 52px; - outline: none !important; - box-shadow: inset 0 0 0 1px $gui-kit-gray-30 !important; - } - - .Select-input { - padding: 0; - margin: 0; - display: flex; - - input { - font-size: 16px; - padding: 0; - height: 22px; - line-height: 22px; - - &:focus { - border: none; - box-shadow: none; - } - } - } - - .Select-value { - .Select-value-label { - height: 22px; - line-height: 22px; - font-size: 16px; - color: $gui-kit-gray-90 !important; - position: absolute; - right: 0; - top: 0; - bottom: 0; - left: 15px; - height: 100%; - display: flex; - align-items: center; - margin: 0; - } - } - - .Select-placeholder, - .Select-value, - .Select-input { - padding: 0 15px !important; - height: 100% !important; - display: flex !important; - align-items: center !important; - } - - .Select-placeholder { - color: $gui-kit-gray-30; - opacity: 1; - text-transform: none; - font-size: 16px; - } - - .Select-multi-value-wrapper { - width: 100% !important; - height: 100% !important; - } - - .Select-arrow-zone { - padding-right: 15px !important; - - .Select-arrow { - background-image: none; - border: none; - width: 15px; - height: 9px; - background-size: 15px 9px; - top: 0 !important; - border-width: 0 !important; - opacity: 0; - pointer-events: none; - } - } - - .Select { - &.is-open { - .Select-arrow-zone { - .Select-arrow { - transform: scale(1, -1); - } - } - } - - &:not(.is-searchable) { - .Select-input { - display: none !important; - } - } - } - - .Select-menu-outer { - top: calc(100% + 2px) !important; - border: 1px solid $gui-kit-gray-30 !important; - border-radius: 0 !important; - max-height: 269px; - z-index: 7; - margin: 0; - - .Select-menu { - padding: 9px 0; - max-height: 269px; - - .Select-option { - padding: 0 15px !important; - font-size: 16px !important; - line-height: 30px !important; - color: $gui-kit-gray-90 !important; - background-color: transparent !important; - text-decoration: none !important; - - &.is-selected { - font-weight: bold !important; - } - - &:hover { - background-color: #229173 !important; - color: $tc-white !important; - } - } - } - } - } - - &.haveError { - :global { - .Select-control { - box-shadow: inset 0 0 0 2px $gui-kit-level-5 !important; - } - } - } - - // lg size - &.lgSize { - :global { - .Select-control { - height: 52px; - } - - .Select-input { - input { - height: 52px; - } - } - } - } - - // xs size - &.xsSize { - padding-top: 0; - - &.haveValue .label, - &.isFocused .label { - margin-top: -12px; - } - - :global { - .Select-control { - height: 40px; - } - - .Select-input { - input { - font-size: 14px; - height: 40px; - } - } - - .Select-value { - .Select-value-label { - font-size: 14px; - color: $gui-kit-gray-90 !important; - } - } - - .Select-placeholder { - font-size: 14px; - } - - .Select-menu-outer { - * { - font-size: 14px !important; - } - - .Select-menu { - .Select-option { - font-size: 14px !important; - } - } - } - } - - .errorMessage { - @include errorMessageXs; - } - } -} diff --git a/src/apps/earn/src/components/DropdownTerms/index.jsx b/src/apps/earn/src/components/DropdownTerms/index.jsx deleted file mode 100644 index d47070913..000000000 --- a/src/apps/earn/src/components/DropdownTerms/index.jsx +++ /dev/null @@ -1,214 +0,0 @@ -/** - * Dropdown terms component. - */ -import React, { useState, useRef, useEffect } from "react"; -import PT from "prop-types"; -import _ from "lodash"; -import Select from "react-select"; - -import config from "../../config"; -import iconDown from "../../assets/icons/dropdown-arrow.png"; - -import styles from "./styles.scss"; -import { styled as styledCss } from "@earn/utils"; -const styled = styledCss(styles) - -function DropdownTerms({ - terms, - placeholder, - label, - required, - onChange, - errorMsg, - addNewOptionPlaceholder, - size, -}) { - const [internalTerms, setInternalTerms] = useState(terms); - const selectedOption = _.filter(internalTerms, { selected: true }).map( - (o) => ({ - value: o.label, - label: o.label, - }) - ); - const [focused, setFocused] = useState(false); - const delayedOnChange = useRef( - _.debounce((q, cb) => cb(q), config.GUIKIT.DEBOUNCE_ON_CHANGE_TIME) // eslint-disable-line no-undef - ).current; - - const containerRef = useRef(null); - let inputFieldRef = useRef(null); - const latestPropsRef = useRef(null); - latestPropsRef.current = { addNewOptionPlaceholder }; - - useEffect(() => { - const selectInput = containerRef.current.getElementsByClassName( - "Select-input" - ); - if (selectInput && selectInput.length) { - inputFieldRef.current = selectInput[0].getElementsByTagName("input"); - inputFieldRef.current[0].placeholder = focused - ? latestPropsRef.current.addNewOptionPlaceholder - : ""; - inputFieldRef.current[0].style.border = "none"; - inputFieldRef.current[0].style.boxShadow = "none"; - selectInput[0].style.borderTop = "none"; - } - }, [focused, selectedOption]); - useEffect(() => { - setInternalTerms(terms); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [terms && terms.length]); - - const CustomReactSelectRow = React.forwardRef( - ({ className, option, children, onSelect }, ref) => - children ? ( - { - event.preventDefault(); - event.stopPropagation(); - onSelect(option, event); - }} - title={option.title} - tabIndex={-1} - > - {children} - - ) : null - ); - - CustomReactSelectRow.defaultProps = { - children: null, - className: "", - onSelect: () => {}, - }; - - CustomReactSelectRow.propTypes = { - children: PT.node, - className: PT.string, - onSelect: PT.func, - option: PT.object.isRequired, - }; - - return ( -
-
- this.setState({ editURL: this.inputURL.value })} - ref={(node) => { - this.inputURL = node; - }} - className={styled("url")} - tabIndex="0" - value={st.editURL} - /> -
-
- Size%: - - this.setState({ size: _.clamp(this.inputSize.value, 0, 100) }) - } - ref={(node) => { - this.inputSize = node; - }} - className={styled("size")} - tabIndex="-1" - value={st.size} - /> -
-
-
- - -
- {st.previewURL ? ( -
-
- {st.description} -
- ) : null} - -
- ); - } -} - -EditModal.defaultProps = { - description: "", - onSave: _.noop, - onCancel: _.noop, - size: 100, - src: "http://", -}; - -EditModal.propTypes = { - description: PT.string, - onSave: PT.func, - onCancel: PT.func, - size: PT.number, - src: PT.string, -}; diff --git a/src/apps/earn/src/components/Editor/Image/EditModal/style.scss b/src/apps/earn/src/components/Editor/Image/EditModal/style.scss deleted file mode 100644 index c3a1aa5ac..000000000 --- a/src/apps/earn/src/components/Editor/Image/EditModal/style.scss +++ /dev/null @@ -1,65 +0,0 @@ -@import "@earn/styles/mixins"; - -.buttons-container { - display: inline-block; - margin-left: 31px; - margin-top: $base-unit * 2; -} - -// Competing against some globals -.container { - @include tc-body-md; - - input { - display: inline-block; - height: $base-unit * 6; - margin: 0; - padding: 0; - margin-left: $base-unit * 1; - vertical-align: middle; - - &.size { - width: 50px; - } - - &.url { - width: 500px; - } - } - - z-index: 1000; -} - -.modalContainer { - top: 30%; - left: 50%; - transform: translate(-50%); - height: auto; - width: 720px; -} - -.modalOverlay { - pointer-events: bounding-box; -} - -.fields-container { - display: flex; - flex-direction: row; - justify-content: space-between; -} - -.field { - display: inline-block; -} - -.preview { - display: block; -} - -.size { - width: 50px; -} - -.url { - width: 100%; -} diff --git a/src/apps/earn/src/components/Editor/Image/Popup/index.jsx b/src/apps/earn/src/components/Editor/Image/Popup/index.jsx deleted file mode 100644 index e01b842d3..000000000 --- a/src/apps/earn/src/components/Editor/Image/Popup/index.jsx +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Popup Component for Link Decorators - */ -import _ from "lodash"; -import PT from "prop-types"; -import React from "react"; - - -import EditModal from "../EditModal"; - -import styles from "./style.scss"; -import { styled as styledCss } from "@earn/utils"; -import { Button } from "~/libs/ui"; -const styled = styledCss(styles) - -export default class Popup extends React.Component { - constructor(props) { - super(props); - this.state = { - editing: props.triggerModal, - }; - } - - render() { - const { onEdit, size, src } = this.props; - const { editing } = this.state; - const renderDisplay = () => ( -
- -
- ); - - const renderEdit = () => ( -
- this.setState({ editing: false })} - onSave={(newSrc, newSize) => { - this.setState({ editing: false }); - onEdit(newSrc, newSize); - }} - /> -
- ); - - return
{editing ? renderEdit() : renderDisplay()}
; - } -} - -Popup.defaultProps = { - size: 100, - src: "http://", - onEdit: _.noop, - triggerModal: false, -}; - -Popup.propTypes = { - onEdit: PT.func, - size: PT.number, - src: PT.string, - triggerModal: PT.bool, -}; diff --git a/src/apps/earn/src/components/Editor/Image/Popup/style.scss b/src/apps/earn/src/components/Editor/Image/Popup/style.scss deleted file mode 100644 index 7fb680869..000000000 --- a/src/apps/earn/src/components/Editor/Image/Popup/style.scss +++ /dev/null @@ -1,6 +0,0 @@ -@import "@earn/styles/mixins"; - -.edit { - height: $base-unit * 4; - width: $base-unit * 4; -} diff --git a/src/apps/earn/src/components/Editor/Image/index.jsx b/src/apps/earn/src/components/Editor/Image/index.jsx deleted file mode 100644 index 5fefc0d78..000000000 --- a/src/apps/earn/src/components/Editor/Image/index.jsx +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Draft Decorator Component - * Renders images within the draft component including a popup edit button and modal - */ -import PT from "prop-types"; - -import { Tooltip } from "~/libs/ui"; - -import Popup from "./Popup"; - -const Image = ({ children, contentState, entityKey, updateEntityData }) => { - const { description, size, src, triggerModal } = contentState - .getEntity(entityKey) - .getData(); - - const popup = ( - { - updateEntityData(entityKey, { src: newSrc, size: newSize }); - }} - size={size} - src={src} - triggerModal={triggerModal} - /> - ); - - return ( - - - {description} - - {children} - - ); -}; - -Image.propTypes = { - contentState: PT.shape().isRequired, - children: PT.node.isRequired, - entityKey: PT.string.isRequired, - updateEntityData: PT.func.isRequired, -}; - -export default Image; diff --git a/src/apps/earn/src/components/Editor/Link/Popup/index.jsx b/src/apps/earn/src/components/Editor/Link/Popup/index.jsx deleted file mode 100644 index 6f6f77a50..000000000 --- a/src/apps/earn/src/components/Editor/Link/Popup/index.jsx +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Popup Component for Link Decorators - */ -import _ from "lodash"; -import PT from "prop-types"; -import React from "react"; - -import styles from "./style.scss"; -import { styled as styledCss } from "@earn/utils"; -import { Button } from "~/libs/ui"; -const styled = styledCss(styles) - -export default class Popup extends React.Component { - constructor(props) { - super(props); - this.state = { - href: props.href, - editing: false, - }; - } - - handleDone() { - const { onEdit } = this.props; - const { href } = this.state; - onEdit(href); - this.setState({ editing: false }); - } - - render() { - const { editing, href } = this.state; - const renderDisplay = () => ( -
- - {href} - - -
- ); - - const renderEdit = () => ( -
- this.setState({ href: this.node.value })} - onKeyUp={(e) => { - if (e.keyCode === 13) { - this.handleDone(); - } - }} - ref={(node) => { - this.node = node; - }} - /> - -
- ); - - return ( -
- {editing ? renderEdit() : renderDisplay()} -
- ); - } -} - -Popup.defaultProps = { - href: "", - onEdit: _.noop, -}; - -Popup.propTypes = { - href: PT.string, - onEdit: PT.func, -}; diff --git a/src/apps/earn/src/components/Editor/Link/Popup/style.scss b/src/apps/earn/src/components/Editor/Link/Popup/style.scss deleted file mode 100644 index 8fb4ddd90..000000000 --- a/src/apps/earn/src/components/Editor/Link/Popup/style.scss +++ /dev/null @@ -1,26 +0,0 @@ -@import "@earn/styles/mixins"; - -.edit { - height: $base-unit * 4; - width: $base-unit * 4; -} - -// Competing against some globals -.container { - a { - display: inline-block; - min-width: $base-unit * 24; - padding-left: $base-unit * 4; - vertical-align: middle; - } - - input[type="text"] { - display: inline-block; - height: $base-unit * 6; - margin: 0; - padding: 0; - margin-left: $base-unit * 1; - width: $base-unit * 48; - vertical-align: middle; - } -} diff --git a/src/apps/earn/src/components/Editor/Link/index.jsx b/src/apps/earn/src/components/Editor/Link/index.jsx deleted file mode 100644 index d36a082cf..000000000 --- a/src/apps/earn/src/components/Editor/Link/index.jsx +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Draft Decorator Component - * Renders links within the draft component including a popup Tooltip to edit - */ -import PT from "prop-types"; - -import { Tooltip } from "~/libs/ui"; - -import Popup from "./Popup"; - -const Link = ({ children, contentState, entityKey, updateEntityData }) => { - const { href, triggerPopup } = contentState.getEntity(entityKey).getData(); - - const popup = ( - updateEntityData(entityKey, { href: updated })} - /> - ); - - return ( - - - - {children} - - - - ); -}; - -Link.defaultProps = { - children: null, -}; - -Link.propTypes = { - children: PT.node, - contentState: PT.shape().isRequired, - entityKey: PT.string.isRequired, - updateEntityData: PT.func.isRequired, -}; - -export default Link; diff --git a/src/apps/earn/src/components/Editor/MarkdownEditor/BlockWrapper.jsx b/src/apps/earn/src/components/Editor/MarkdownEditor/BlockWrapper.jsx deleted file mode 100644 index 257fa80c6..000000000 --- a/src/apps/earn/src/components/Editor/MarkdownEditor/BlockWrapper.jsx +++ /dev/null @@ -1,37 +0,0 @@ -import PT from "prop-types"; -import React from "react"; - -import { EditorBlock } from "draft-js"; - -export default function BlockWrapper(props) { - const { blockProps } = props; - const { type } = blockProps; - const types = type.split("-"); - const thisType = (types[0] || "unstyled").split(":"); - - const leadingLi = - thisType[0] === "li" && !types.slice(1).some((x) => x === "li"); - - if (thisType[0] !== "unstyled") { - const child = BlockWrapper({ - ...props, - blockProps: { - type: types.slice(1).join("-"), - }, - }); - let className = thisType[0]; - if (thisType[1]) className += ` md-syntax-level-${thisType[1]}`; - if (leadingLi) className += " leadingLi"; - if (thisType[0] === "hr") { - return
{child}
; - } - return React.createElement(thisType[0], { className }, child); - } - return ; -} - -BlockWrapper.propTypes = { - blockProps: PT.shape({ - type: PT.string.isRequired, - }).isRequired, -}; diff --git a/src/apps/earn/src/components/Editor/MarkdownEditor/index.jsx b/src/apps/earn/src/components/Editor/MarkdownEditor/index.jsx deleted file mode 100644 index 015d46a62..000000000 --- a/src/apps/earn/src/components/Editor/MarkdownEditor/index.jsx +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Markdown editor. - */ - -import PT from "prop-types"; -import React from "react"; -import Turndown from "turndown"; - -import { ContentState, convertFromHTML, EditorState } from "draft-js"; - -import BlockWrapper from "./BlockWrapper"; -import Connector from "../Connector"; -import GenericEditor from "../GenericEditor"; -import MdUtils from "./md-utils"; - -import ImageEditor from "../Image/EditModal"; - -import styles from "./style.scss"; -import { styled as styledCss } from "../../../utils"; -const styled = styledCss(styles) - -const GUI_STATES = { - IMAGE_EDIT_MODAL: "IMAGE_EDIT_MODAL", - REGULAR: "REGULAR", -}; - -export default class MarkdownEditor extends React.Component { - constructor(props) { - super(props); - this.mdUtils = new MdUtils(); - this.state = { - editor: EditorState.createEmpty(this.mdUtils), - gui: GUI_STATES.REGULAR, - }; - this.turndown = new Turndown(); - } - - componentDidMount() { - const { initialContent } = this.props; - if (initialContent) { - let state = initialContent.replace(/\n/g, "
"); - state = convertFromHTML(state); - state = ContentState.createFromBlockArray( - state.contentBlocks, - state.entityMap - ); - state = EditorState.createWithContent(state, this.mdUtils); - this.onChange(state); - } - } - - onChange(newState) { - const { connector } = this.props; - this.mdUtils.parse(newState.getCurrentContent()); - if (connector && connector.previewer) { - connector.previewer.setVisible(true); - connector.previewer.setContent(this.mdUtils.getHtml()); - } - if (this.editor) { - const selfState = this.mdUtils.highlight(newState); - if (selfState !== newState) { - this.setState({ editor: selfState }); - } - } - } - - getHtml() { - return this.mdUtils.getHtml(); - } - - setHtml(html) { - let state = this.turndown.turndown(html); - state = state.replace(/\n/g, "
"); - state = convertFromHTML(state); - state = ContentState.createFromBlockArray( - state.contentBlocks, - state.entityMap - ); - state = EditorState.createWithContent(state, this.mdUtils); - this.onChange(state); - } - - insertImage() { - setImmediate(() => this.setState({ gui: GUI_STATES.IMAGE_EDIT_MODAL })); - } - - render() { - const { connector, id } = this.props; - const st = this.state; - return ( -
- {st.gui === GUI_STATES.IMAGE_EDIT_MODAL ? ( - this.setState({ gui: GUI_STATES.REGULAR })} - /> - ) : null} - ({ - component: BlockWrapper, - editable: true, - props: { type: block.getType() }, - })} - connector={connector} - decorator={this.mdUtils} - editorState={st.editor} - id={id} - onChange={(state) => this.onChange(state)} - ref={(node) => { - this.editor = node; - }} - /> -
- ); - } -} - -MarkdownEditor.defaultProps = { - connector: new Connector(), - id: null, - initialContent: null, -}; - -MarkdownEditor.propTypes = { - connector: PT.shape(), - id: PT.string, - initialContent: PT.string, -}; diff --git a/src/apps/earn/src/components/Editor/MarkdownEditor/inlineWrapperFactory.jsx b/src/apps/earn/src/components/Editor/MarkdownEditor/inlineWrapperFactory.jsx deleted file mode 100644 index fe0684052..000000000 --- a/src/apps/earn/src/components/Editor/MarkdownEditor/inlineWrapperFactory.jsx +++ /dev/null @@ -1,35 +0,0 @@ -import PT from "prop-types"; -import React from "react"; - -function InlineWrapper({ children, hrefs, key }) { - if (!key || key === "text") { - return {children}; - } - if (key === "mdSyntax") { - return {children}; - } - if (key === "inlineCode") { - return {children}; - } - const keys = key.split("-"); - const child = InlineWrapper({ - children, - key: keys.slice(1).join("-"), - }); - - if (keys[0].startsWith("a:")) { - return {children}; - } - - return React.createElement(keys[0], {}, child); -} - -InlineWrapper.propTypes = { - children: PT.node.isRequired, - hrefs: PT.shape.isRequired, - key: PT.string.isRequired, -}; - -export default function inlineWrapperFactory(key, hrefs) { - return ({ children }) => InlineWrapper({ children, hrefs, key }); -} diff --git a/src/apps/earn/src/components/Editor/MarkdownEditor/md-utils.js b/src/apps/earn/src/components/Editor/MarkdownEditor/md-utils.js deleted file mode 100644 index 23689f398..000000000 --- a/src/apps/earn/src/components/Editor/MarkdownEditor/md-utils.js +++ /dev/null @@ -1,456 +0,0 @@ -/** - * Markdown utilities for DraftJS editor. - * - * There are plenty of Markdown-related plugins for DraftJS in Internet, but it - * looks like nobody made it right so far; thus here we go with the correct - * solution. - */ - -import _ from "lodash"; -import Markdown from "markdown-it"; - -import { EditorState, SelectionState, Modifier } from "draft-js"; - -import { List } from "immutable"; - -import shortId from "shortid"; - -import inlineWrapperFactory from "./inlineWrapperFactory"; - -import "./style.scss"; - -/** - * Counts the specified characters in the given string. - * @param {String} string - * @param {String} char - * @return {Number} - */ -function count(string, char) { - let pos = -1; - let res = 0; - for (;;) { - const c = string[(pos += 1)]; - if (c === char) res += 1; - else if (!c) return res; - } -} - -/** - * Finds position of the n-th occurance of the specified character in the given - * string, and returns it. Returns -1, if not found. - * @param {String} string - * @param {String} char - * @param {Number} n - * @return {Number} - */ -function findNth(string, char, n) { - let pos = -1; - let res = n; - for (;;) { - const c = string[(pos += 1)]; - if (c === char) res -= 1; - if (!res) return pos; - if (!c) return -1; - } -} - -/* Internal. */ -function newSelector(key, pos = 0, end, endKey) { - return SelectionState.createEmpty(key).merge({ - anchorOffset: pos, - focusKey: endKey === undefined ? key : endKey, - focusOffset: end === undefined ? pos : end, - }); -} - -/* Internal. */ -function insertChar(content, key, pos, char) { - return Modifier.insertText(content, newSelector(key, pos), char); -} - -/* Internal. */ -function mergeBlockData(content, key, data) { - return Modifier.mergeBlockData(content, newSelector(key), data); -} - -/* Internal. */ -function removeChar(content, key, pos) { - return Modifier.removeRange(content, newSelector(key, pos, pos + 1)); -} - -/* Internal. */ -function removeLastChar(content, key, pos) { - const s = newSelector(key, pos, 0, content.getKeyAfter(key)); - return Modifier.removeRange(content, s); -} - -/* Internal. */ -function splitBlock(content, key, pos) { - return Modifier.splitBlock(content, newSelector(key, pos)); -} - -/* Internal. */ -function setBlockType(content, key, type) { - const block = content.getBlockForKey(key); - if (block.getType() === type) return content; - return Modifier.setBlockType(content, newSelector(key, 0), type); -} - -export default class MdUtils { - /** - * Constructs a new MdUtils instance. - * @param {ContentState} contentState Optional. If provided, constructor will - * automatically call parse(contentState) in the end of initialization. - */ - constructor(contentState) { - this.markdown = new Markdown(); - this.markdown.disable(["table"]); - this.tokens = []; - if (contentState) this.parse(contentState); - } - - /** - * Private. - * - * Merges and/or splits DraftJS blocks to ensure that the current block - * contains exactly the specified number of lines. - * - * @param {Number} numLines - */ - alignLines(numLines) { - for (;;) { - const text = this.content.getBlockForKey(this.key).getText(); - const numLinesInBlock = 1 + count(text, "\n"); - if (numLinesInBlock < numLines) { - const nextKey = this.content.getKeyAfter(this.key); - if (!nextKey) return; - this.content = removeLastChar(this.content, this.key, text.length); - this.content = insertChar(this.content, this.key, text.length, "\n"); - if (this.selection.getAnchorKey() === nextKey) { - this.selection = this.selection.merge({ - anchorKey: this.key, - anchorOffset: this.selection.getAnchorOffset() + text.length + 1, - }); - } - if (this.selection.getFocusKey() === nextKey) { - this.selection = this.selection.merge({ - focusKey: this.key, - focusOffset: this.selection.getFocusOffset() + text.length + 1, - }); - } - } else if (numLinesInBlock > numLines) { - const splitPoint = findNth(text, "\n", numLines); - this.content = removeChar(this.content, this.key, splitPoint); - this.content = splitBlock(this.content, this.key, splitPoint); - if ( - this.selection.getAnchorKey() === this.key && - this.selection.getAnchorOffset() > splitPoint - ) { - this.selection = this.selection.merge({ - anchorKey: this.content.getKeyAfter(this.key), - anchorOffset: this.selection.getAnchorOffset() - splitPoint - 1, - }); - } - if ( - this.selection.getFocusKey() === this.key && - this.selection.getFocusOffset() > splitPoint - ) { - this.selection = this.selection.merge({ - focusKey: this.content.getKeyAfter(this.key), - focusOffset: this.selection.getFocusOffset() - splitPoint - 1, - }); - } - } else return; - } - } - - getDecorations(block) { - _.noop(this); - const res = block.getData().get("decorations") || List(); - return res.setSize(block.getLength()); - } - - getComponentForKey(key) { - _.noop(this); - return inlineWrapperFactory(key, this.hrefs); - } - - getPropsForKey() { - _.noop(this); - return {}; - } - - /** - * Highlights Markdown syntax in the given DraftJS state. - * @param {EditorState} state DraftJS EditorState that holds a plain text with - * a valid Markdown markup, previously loaded into this MdUtils instance via - * parse(..) method. - * @return {EditorState} Resulting state with the proper formatting and - * styling of the markup. - */ - highlight(state) { - this.blockTypes = []; - this.content = state.getCurrentContent(); - this.decorations = {}; - this.decoreLevel = 0; - this.endLines = []; - this.hrefs = {}; - this.key = this.content.getFirstBlock().getKey(); - this.selection = state.getSelection(); - this.styleLine = 0; - this.tokenId = 0; - this.tokens.forEach(() => this.highlightNextToken()); - while (this.key) { - this.content = setBlockType(this.content, this.key, "unstyled"); - this.highlightInline(); - this.key = this.content.getKeyAfter(this.key); - } - const res = EditorState.push(state, this.content, "custom"); - return EditorState.acceptSelection(res, this.selection); - } - - /** - * Private. - * - * Highlights inline Markdown syntax in the current DraftJS block. - */ - highlightInline(subTokens) { - let pos = 0; - const text = this.content.getBlockForKey(this.key).getText(); - const res = new Array(text.length); - res.fill("mdSyntax"); - - if (subTokens) { - const styles = []; - subTokens.forEach((st) => { - switch (st.type) { - case "link_open": { - const id = shortId().replace(/-/g, ":"); - [[, this.hrefs[id]]] = st.attrs; - styles.push(`a:${id}`); - break; - } - - case "em_open": - case "strong_open": - styles.push(st.tag); - break; - - case "s_open": - styles.push("strike"); - break; - - case "em_close": - case "link_close": - case "s_close": - case "strong_close": - styles.pop(); - break; - - case "code_inline": { - if (!st.content.length) break; - let style = styles.join("-"); - if (!style) style = "inlineCode"; - else style = `${style}-inlineCode`; - pos = text.indexOf(st.content, pos); - const end = pos + st.content.length; - while (pos < end) { - res[pos] = style; - pos += 1; - } - break; - } - - case "text": { - if (!st.content.length) break; - pos = text.indexOf(st.content, pos); - const end = pos + st.content.length; - const style = styles.join("-") || "text"; - while (pos < end) { - res[pos] = style; - pos += 1; - } - if (styles.length && _.last(styles).startsWith("a:")) { - pos += 3 + _.last(styles).slice(2).length; - } - break; - } - - default: - } - }); - } - - let i = text.length - 1; - while (i >= 0 && text[i] === " ") { - res[i] = "text"; - i -= 1; - } - while (i >= 0 && res[i] === "mdSyntax") i -= 1; - if (i < 0) res.fill("mdSyntax"); - - const decorations = List(res); - this.content = mergeBlockData(this.content, this.key, { decorations }); - } - - /** - * Private. - * - * Highlights Markdown syntax in the specified number of lines, starting from - * the first non-styled line. - * - * @param {Number} numLines - */ - highlightLines(numLines) { - this.alignLines(numLines); - const type = this.blockTypes.join("-") || "unstyled"; - this.content = setBlockType(this.content, this.key, type); - } - - /** - * Private. - * - * Highlights all Markdown syntax between the last highlighted DraftJS block - * and the current MarkdownIt token. - */ - highlightNextToken() { - const token = this.tokens[this.tokenId]; - - /* If token opens a new range, we: - * - Style any block before this token line; - * - Remember the range of this token, and the line of the first non-styled - * block. */ - if (token.map) { - const linesBefore = token.map[0] - this.styleLine; - if (linesBefore) { - this.highlightLines(linesBefore); - this.highlightInline(); - [this.styleLine] = token.map; - this.key = this.content.getKeyAfter(this.key); - } - } - - switch (token.type) { - case "blockquote_open": - case "bullet_list_open": - this.decoreLevel += 1; - this.blockTypes.push(`${token.tag}:${this.decoreLevel}`); - this.endLines.push(token.map[1]); - break; - - case "ordered_list_open": - this.decoreLevel += 2; - this.blockTypes.push(`${token.tag}:${this.decoreLevel}`); - this.endLines.push(token.map[1]); - break; - - case "heading_open": - case "list_item_open": - case "paragraph_open": - this.blockTypes.push(token.tag); - this.endLines.push(token.map[1]); - break; - - case "blockquote_close": - case "bullet_list_close": - case "heading_close": - case "list_item_close": - case "ordered_list_close": - case "paragraph_close": { - const linesBefore = _.last(this.endLines) - this.styleLine; - if (linesBefore) { - this.highlightLines(linesBefore); - this.highlightInline(); - this.key = this.content.getKeyAfter(this.key); - this.styleLine = _.last(this.endLines); - } - this.blockTypes.pop(); - this.endLines.pop(); - - switch (token.type) { - case "blockquote_close": - case "bullet_list_close": - this.decoreLevel -= 1; - break; - case "ordered_list_close": - this.decoreLevel -= 2; - break; - default: - } - - break; - } - - case "fence": { - this.blockTypes.push(token.tag); - let numLines = token.map[1] - token.map[0]; - const subTokens = [{ type: "text", content: token.content }]; - const isClosed = 1 + count(token.content, "\n") < numLines; - if (!isClosed) { - let i = 1 + this.tokenId; - while (i < this.tokens.length && !this.tokens[i].map) i += 1; - if ( - i === this.tokens.length || - this.tokens[i].map[0] > token.map[1] - ) { - numLines += 1; - } - } - this.highlightLines(numLines); - this.highlightInline(subTokens); - this.key = this.content.getKeyAfter(this.key); - [, this.styleLine] = token.map; - this.blockTypes.pop(); - break; - } - - case "code_block": - case "hr": { - this.blockTypes.push(token.tag); - this.highlightLines(token.map[1] - token.map[0]); - this.highlightInline(token.children); - this.key = this.content.getKeyAfter(this.key); - [, this.styleLine] = token.map; - this.blockTypes.pop(); - break; - } - - case "inline": - this.highlightLines(token.map[1] - token.map[0]); - this.highlightInline(token.children); - this.key = this.content.getKeyAfter(this.key); - [, this.styleLine] = token.map; - break; - - default: - } - - this.tokenId += 1; - } - - /** - * Returns HTML representation of the Markdown markup previously loaded by - * parse(..) method of MdUtils. - * @return {String} - */ - getHtml() { - if (!this.html) { - this.html = this.markdown.renderer.render(this.tokens, this.env); - } - return this.html; - } - - /** - * Parses the given DraftJS state. The state should contain a plain text with - * Markdown markup. After the parse you can call other methods of MdUtils to - * generate corresponding HTML markup, or DraftJS state for rendered Markdown - * representation, or DraftJS decorator for Markdown syntax highlighting in - * the original state. - * @param {ContentState} state - */ - parse(contentState) { - delete this.html; - this.env = {}; - this.tokens = this.markdown.parse(contentState.getPlainText(), this.env); - } -} diff --git a/src/apps/earn/src/components/Editor/MarkdownEditor/style.scss b/src/apps/earn/src/components/Editor/MarkdownEditor/style.scss deleted file mode 100644 index 24c5e4fe3..000000000 --- a/src/apps/earn/src/components/Editor/MarkdownEditor/style.scss +++ /dev/null @@ -1,148 +0,0 @@ -@import "@earn/styles/mixins"; - -/* Styling of Markdown syntax highlighting. */ -.container { - @include tc-body-md; - - overflow: hidden; - - a { - color: $tc-dark-blue-110; - text-decoration: underline; - } - - code { - background: $tc-gray-neutral-light; - border: 1px solid $tc-gray-20; - border-radius: 6px; - display: block; - font-family: "Roboto Mono", monospace; - padding: 15px 20px; - white-space: pre-wrap; - - &:global.inline { - background: $tc-gray-10; - border: none; - border-radius: 0; - display: inline; - padding: 0 5px; - } - } - - em { - font-style: italic; - } - strong { - font-weight: bold; - } - - /* This styling leads to some artefacts :( */ - :global .hr { - width: 100%; - - > div > span > span { - border-top: 1px solid firebrick; - display: inline-block; - height: 0; - line-height: 0; - width: 100%; - } - } - - :global { - li { - display: inline-block; - position: relative; - - &.leadingLi { - margin-top: 10px; - } - - &::before { - border-left: 3px solid $tc-gray-40; - content: ""; - height: 100%; - position: absolute; - } - } - - $offset: -15; - - @for $level from 0 to 10 { - ol.md-syntax-level-#{$level} > li::before { - left: #{$offset}px; - } - $offset: $offset + 18; - } - - $offset: -5; - - @for $level from 0 to 10 { - ul.md-syntax-level-#{$level} > li::before { - left: #{$offset}px; - } - $offset: $offset + 18; - } - - .blockquote { - position: relative; - - &::before { - border-left: 3px solid $tc-light-blue; - content: ""; - height: 100%; - position: absolute; - } - } - - $offset: -15; - - @for $level from 0 to 10 { - .blockquote.md-syntax-level-#{$level}::before { - left: #{$offset}px; - } - $offset: $offset + 18; - } - - div, - .h1, - .h2, - .h3, - .h4, - .h5, - .h6, - p { - @include tc-body-md; - - margin: 0 !important; - text-transform: none; - } - - .h1 .text { - @include tc-heading-xl; - } - .h2 .text { - @include tc-heading-lg; - } - .h3 .text { - @include tc-heading-md; - } - .h4 .text { - @include tc-heading-sm; - } - .h5 .text { - @include tc-heading-xs; - } - .h6 .text { - @include tc-heading-xs; - } - - .mdSyntax { - @include tc-body-md; - - color: firebrick; - font-family: "Roboto Mono", monospace; - font-weight: bold; - } - } -} diff --git a/src/apps/earn/src/components/Editor/MultiEditor.jsx b/src/apps/earn/src/components/Editor/MultiEditor.jsx deleted file mode 100644 index d5b236874..000000000 --- a/src/apps/earn/src/components/Editor/MultiEditor.jsx +++ /dev/null @@ -1,164 +0,0 @@ -/** - * The MultiEditor component combines together WysiwygEditor and Markdown editor - * allowing to easily switch between them. - */ - -import PT from "prop-types"; -import React from "react"; -import Turndown from "turndown"; - -import { OrderedSet } from "immutable"; - -import Connector from "./Connector"; -import MarkdownEditor from "./MarkdownEditor"; -import WysiwygEditor from "."; - -export const MODES = { - MARKDOWN: "MARKDOWN", - WYSIWYG: "WYSIWYG", -}; - -export default class MultiEditor extends React.Component { - constructor(props) { - super(props); - this.fakeConnector = new Connector(); - this.fakeConnector.setToolbar(this); - this.id = props.id; - this.state = { - mode: props.initialMode, - }; - this.turndown = new Turndown(); - } - - componentDidMount() { - const { connector } = this.props; - if (connector) { - connector.addEditor(this); - this.fakeConnector.setPreviewer(connector.previewer); - } - } - - componentWillReceiveProps({ connector, id }) { - const { connector: prevConnector } = this.props; - this.id = id; - if (connector !== prevConnector) { - if (prevConnector) prevConnector.removeEditor(this); - if (connector) { - connector.addEditor(this); - this.fakeConnector.setPreviewer(connector.previewer); - } - } - } - - componentWillUnmount() { - const { connector } = this.props; - if (connector) connector.removeEditor(this); - } - - onFocusedEditorChanged(state) { - const { connector } = this.props; - if (connector) connector.setFocusedEditor(this, state); - } - - getHtml() { - return this.editor.getHtml(); - } - - setHtml(html) { - this.editor.setHtml(html); - } - - setMode(value) { - const { mode } = this.state; - if (value === mode) return; - const { connector } = this.props; - const state = this.editor.getHtml(); - this.setState({ mode: value }, () => { - this.editor.setHtml(state); - if (connector) connector.setFocusedEditor(this, this.editor.state.editor); - }); - } - - applyBlockStyle(type) { - const { mode } = this.state; - if (mode === MODES.WYSIWYG) this.editor.applyBlockStyle(type); - } - - applyColorStyle(type, color) { - const { mode } = this.state; - if (mode === MODES.WYSIWYG) { - this.editor.applyColorStyle(type, color); - } - } - - focus() { - const { mode } = this.state; - if (mode === MODES.WYSIWYG) this.editor.focus(); - } - - insertImage(src, triggerModal) { - const { mode } = this.state; - switch (mode) { - case MODES.WYSIWYG: - return this.editor.insertImage(src, triggerModal); - case MODES.MARKDOWN: - return this.editor.insertImage(); - default: - return undefined; - } - } - - insertLink(title, href, triggerPopup) { - const { mode } = this.state; - if (mode === MODES.WYSIWYG) { - this.editor.insertLink(title, href, triggerPopup); - } - } - - toggleInlineStyle(styleName) { - const { mode } = this.state; - if (mode === MODES.WYSIWYG) { - return this.editor.toggleInlineStyle(styleName); - } - return OrderedSet(); - } - - render() { - const { mode } = this.state; - switch (mode) { - case MODES.MARKDOWN: - return ( - { - if (node) this.editor = node; - }} - /> - ); - case MODES.WYSIWYG: { - return ( - { - if (node) this.editor = node; - }} - /> - ); - } - default: - throw new Error("Unknown mode"); - } - } -} - -MultiEditor.defaultProps = { - connector: null, - id: null, - initialMode: MODES.WYSIWYG, -}; - -MultiEditor.propTypes = { - connector: PT.shape(), - id: PT.string, - initialMode: PT.oneOf(Object.values(MODES)), -}; diff --git a/src/apps/earn/src/components/Editor/Previewer/index.jsx b/src/apps/earn/src/components/Editor/Previewer/index.jsx deleted file mode 100644 index 526885fb4..000000000 --- a/src/apps/earn/src/components/Editor/Previewer/index.jsx +++ /dev/null @@ -1,54 +0,0 @@ -import PT from "prop-types"; -import React from "react"; - -import styles from "./style.scss"; -import { styled as styledCss } from "../../../utils"; -const styled = styledCss(styles) - -export default class Previewer extends React.Component { - constructor(props) { - super(props); - if (props.connector) props.connector.setPreviewer(this); - this.state = { - content: props.initialContent, - visible: false, - }; - } - - setContent(content) { - setImmediate(() => this.setState({ content })); - } - - setVisible(newVisible) { - const { visible } = this.state; - if (newVisible === visible) return; - setImmediate(() => this.setState({ visible: newVisible })); - } - - render() { - const { content, visible } = this.state; - return ( -
- {visible ? ( -
-
Rendering Preview
-
-
- ) : null} -
- ); - } -} - -Previewer.defaultProps = { - connector: null, - initialContent: "", -}; - -Previewer.propTypes = { - connector: PT.shape(), - initialContent: PT.string, -}; diff --git a/src/apps/earn/src/components/Editor/Previewer/style.scss b/src/apps/earn/src/components/Editor/Previewer/style.scss deleted file mode 100644 index d1776dade..000000000 --- a/src/apps/earn/src/components/Editor/Previewer/style.scss +++ /dev/null @@ -1,25 +0,0 @@ -@import "@earn/styles/mixins"; - -.container { - background: white; - overflow: hidden; - position: relative; -} - -.content { - border: 1px solid $tc-gray-40; - height: 33vh; - margin: 0 0 10px; - padding-top: 25px; -} - -.title { - @include tc-label-xs; - - background: $tc-gray-neutral-dark; - color: $tc-gray-40; - left: 1px; - position: absolute; - padding: 5px 10px; - top: 1px; -} diff --git a/src/apps/earn/src/components/Editor/Toolbar/ColorPicker/index.jsx b/src/apps/earn/src/components/Editor/Toolbar/ColorPicker/index.jsx deleted file mode 100644 index 4bc508922..000000000 --- a/src/apps/earn/src/components/Editor/Toolbar/ColorPicker/index.jsx +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Component - * Implements a color picker dropdown populated with the colors found in utils/editor - */ -import _ from "lodash"; -import PT from "prop-types"; -import React from "react"; -import * as Colors from "react-color"; - -import { EDITOR_COLOR_MAP } from "../../../../utils/editor"; - -import styles from "./style.scss"; -import { styled as styledCss } from "@earn/utils"; -const styled = styledCss(styles) - -const ColorPicker = ({ onChange, style, visible }) => ( -
- {visible ? ( - { - e.preventDefault(); - onChange(_.findKey(EDITOR_COLOR_MAP, (value) => value === hex)); - }} - className={styled("color-picker")} - /> - ) : null} -
-); - -ColorPicker.defaultProps = { - onChange: _.noop, - style: "", - visible: false, -}; - -ColorPicker.propTypes = { - onChange: PT.func, - style: PT.string, - visible: PT.bool, -}; - -export default ColorPicker; diff --git a/src/apps/earn/src/components/Editor/Toolbar/ColorPicker/style.scss b/src/apps/earn/src/components/Editor/Toolbar/ColorPicker/style.scss deleted file mode 100644 index a3272ea00..000000000 --- a/src/apps/earn/src/components/Editor/Toolbar/ColorPicker/style.scss +++ /dev/null @@ -1,7 +0,0 @@ -// The react-color component has inline styles which we need to overwrite -.color-picker { - display: inline-flex !important; - flex-wrap: nowrap !important; - position: absolute !important; - width: auto !important; -} diff --git a/src/apps/earn/src/components/Editor/Toolbar/index.jsx b/src/apps/earn/src/components/Editor/Toolbar/index.jsx deleted file mode 100644 index 587f11dd5..000000000 --- a/src/apps/earn/src/components/Editor/Toolbar/index.jsx +++ /dev/null @@ -1,298 +0,0 @@ -/** - * Component - * Implements a Toolbar that can control multiple components - */ -import _ from "lodash"; -import PT from "prop-types"; -import React from "react"; -import Sticky from "react-stickynode"; - -import { Button } from "~/libs/ui"; -import Select from "../../Select"; -import { EDITOR_BLOCK_STYLE_MAP } from "../../../utils/editor"; - -import { RichUtils } from "draft-js"; - -import ColorPicker from "./ColorPicker"; -import Connector from "../Connector"; -import MultiEditor, { MODES } from "../MultiEditor"; - -import styles from "./style.scss"; -import { styled as styledCss } from "../../../utils"; -const styled = styledCss(styles) - -/** - * Component class, provides a Toolbar that can control multiple Editor components - * connected to it via the Connector class - */ -export default class Toolbar extends React.Component { - constructor(props) { - super(props); - this.state = { - block: null, - editor: null, - // markdown: false, - pickingTextColor: false, - // pickingHighlightColor: false, - - BOLD: false, - ITALIC: false, - }; - } - - componentDidMount() { - const { connector } = this.props; - connector.setToolbar(this); - } - - componentWillReceiveProps({ connector: newConnector }) { - const { connector } = this.props; - const prevConnector = connector; - if (newConnector !== prevConnector) { - if (prevConnector) prevConnector.setToolbar(null); - if (newConnector) newConnector.setToolbar(this); - } - } - - componentWillUnmount() { - const { connector } = this.props; - connector.setToolbar(null); - } - - onFocusedEditorChanged(newState) { - const { connector } = this.props; - const editor = connector.focusedEditor; - if (editor) { - const inlineStyle = newState.getCurrentInlineStyle(); - const block = RichUtils.getCurrentBlockType(newState); - this.setState({ - editor, - block, - BOLD: inlineStyle.has("BOLD"), - // INLINE_CODE: inlineStyle.has('CODE'), - ITALIC: inlineStyle.has("ITALIC"), - UNDERLINE: inlineStyle.has("UNDERLINE"), - STRIKETHROUGH: inlineStyle.has("STRIKETHROUGH"), - }); - } else { - this.setState({ - block: "unstyled", - editor: null, - BOLD: false, - // INLINE_CODE: false, - ITALIC: false, - UNDERLINE: false, - STRIKETHROUGH: false, - }); - } - } - - render() { - const st = this.state; - const disableStyling = !st.editor; - const { connector, nodeId, onSave } = this.props; - - const createStyleButton = (label, name, active, className) => ( - - ); - - return ( - -
- {connector.focusedEditor instanceof MultiEditor ? ( -
- { - st.editor.applyBlockStyle(value); - this.setState({ block: value }); - }} - onFocus={(e) => e.preventDefault()} - options={_.map(EDITOR_BLOCK_STYLE_MAP, (label, value) => ({ - label, - value, - }))} - placeholder="Block Style" - value={st.editor ? st.block : null} - /> -
- - {/* I guess, we gonna drop the inline Markdown option. Just for - * a case, let's keep the button code around for a bit longer. */ - /* - - */} -
-
- ); - } -} - -Toolbar.defaultProps = { - connector: new Connector(), - onSave: _.noop, - nodeId: null, -}; - -Toolbar.propTypes = { - connector: PT.instanceOf(Connector), - onSave: PT.func, - nodeId: PT.string, -}; diff --git a/src/apps/earn/src/components/Editor/Toolbar/style.scss b/src/apps/earn/src/components/Editor/Toolbar/style.scss deleted file mode 100644 index 07db930cc..000000000 --- a/src/apps/earn/src/components/Editor/Toolbar/style.scss +++ /dev/null @@ -1,81 +0,0 @@ -@import "@earn/styles/mixins"; - -@mixin button { - margin: 0 1px; - padding: 0 10px; -} - -.basic { - @include button; -} - -.bold { - @include button; - - font-weight: bold; -} - -.container { - background: $tc-gray-neutral-light; - padding: 10px; - width: 100%; -} - -.highlight-color-picker { - display: inline; - position: absolute; - transform: translate(-56px, 44px); -} - -.inlineCode { - @include button; - @include roboto-mono-regular; -} - -.italic { - @include button; - - font-style: italic; -} - -.select { - @include tc-label-md; -} - -.select-wrapper { - @include button; - - display: inline-block; - vertical-align: middle; - width: 250px; -} - -.save { - @include button; -} - -.separator { - display: inline-block; - height: 30px; - margin: 0 5px; - vertical-align: middle; - width: 1px; -} - -.strikethrough { - @include button; - - text-decoration: line-through; -} - -.text-color-picker { - display: inline; - position: absolute; - transform: translate(-44px, 44px); -} - -.underline { - @include button; - - text-decoration: underline; -} diff --git a/src/apps/earn/src/components/Editor/index.jsx b/src/apps/earn/src/components/Editor/index.jsx deleted file mode 100644 index bf12218ea..000000000 --- a/src/apps/earn/src/components/Editor/index.jsx +++ /dev/null @@ -1,306 +0,0 @@ -/** - * Content editor based on DraftJS. - * - * DraftJS is not Redux-friendly, thus our editor uses local state, unlike most - * of our code. Technically, it is possible to keep its state in Redux store, - * but it will have performance drawback, as it will demand constant conversions - * between the Redux state segment and the internal state of the editor. - */ -import _ from "lodash"; -import PT from "prop-types"; -import React from "react"; - -import { - ContentState, - convertFromHTML, - EditorState, - Modifier, - RichUtils, -} from "draft-js"; -import "draft-js/dist/Draft.css"; - -import Editor from "draft-js-plugins-editor"; -import createMarkdownShortcutsPlugin from "draft-js-markdown-shortcuts-plugin"; - -import { EDITOR_COLOR_MAP, editorStateToHTML } from "../../utils/editor"; - -import Connector from "./Connector"; -import createCustomPlugin from "./plugin"; - -import styles from "./style.scss"; - -export default class EditorWrapper extends React.Component { - constructor(props) { - super(props); - this.id = props.id; - - this.state = { - editor: EditorState.createEmpty(), - markdown: false, - }; - - // Each Editor needs its own instance of plugins - this.markdownPlugin = createMarkdownShortcutsPlugin(); - // We need to inject the EditorWrapper into the plugin so that it can - // modify state.editorState - this.customPlugin = createCustomPlugin({ - editor: this, - }); - } - - componentDidMount() { - const { connector, initialContent } = this.props; - connector.addEditor(this); - if (initialContent) { - let editorState = convertFromHTML(initialContent); - editorState = ContentState.createFromBlockArray( - editorState.contentBlocks, - editorState.entityMap - ); - editorState = EditorState.createWithContent(editorState); - this.initialContent = editorState.getCurrentContent(); - setImmediate(() => this.setState({ editor: editorState })); - } - } - - componentWillReceiveProps({ connector, id }) { - const { connector: prevConnector } = this.props; - this.id = id; - if (connector !== prevConnector) { - if (prevConnector) prevConnector.removeEditor(this); - if (connector) connector.addEditor(this); - } - } - - componentWillUnmount() { - const { connector } = this.props; - connector.removeEditor(this); - } - - getHtml() { - const { editor } = this.state; - return editorStateToHTML(editor.getCurrentContent()); - } - - setHtml(html) { - let state = convertFromHTML(html); - state = ContentState.createFromBlockArray( - state.contentBlocks, - state.entityMap - ); - state = EditorState.createWithContent(state); - setImmediate(() => this.setState({ editor: state })); - } - - /** - * Sets the block type/style at the current selection. Type map can be found in utils/editor. - * Only one block type/style can be applied, this will replace the previous. - * @param {String} type The new block style - */ - applyBlockStyle(type) { - const { editor } = this.state; - let editorState = editor; - editorState = RichUtils.toggleBlockType(editorState, type); - this.setState({ editorState }); // eslint-disable-line - } - - /** - * Sets the color at the current selection for the specified category. - * Type map can be found in utils/editor. - * @param {String} type Category, TEXT or HIGHLIGHT - * @param {String} color The new color name - */ - applyColorStyle(type, color) { - let { editor: editorState } = this.state; - let contentState = editorState.getCurrentContent(); - - const sel = editorState.getSelection(); - - // Clear any existing colors - contentState = _.reduce( - EDITOR_COLOR_MAP, - (state, value, name) => - Modifier.removeInlineStyle(state, sel, `${type}_${name}`), - contentState - ); - - editorState = EditorState.push( - editorState, - contentState, - "change-inline-style" - ); - - // Apply new color - editorState = RichUtils.toggleInlineStyle(editorState, `${type}_${color}`); - - this.setState({ editor: editorState }); - } - - focus() { - if (this.node) this.node.focus(); - } - - /** - * Inserts a new image at current cursor selection. - * @param {String} src The default src - * @param {Boolean} triggerModal Whether to trigger the img selection/resize modal on creation - */ - insertImage(src, triggerModal) { - let { editor: editorState } = this.state; - let contentState = editorState.getCurrentContent(); - - // If the user has a range selected, it needs to be collapsed before insertText will work - // This sets the starting and ending range to the same position, - // which is equivalent to just a cursor/caret - let sel = editorState.getSelection(); - const startKey = sel.getStartKey(); - const startOffset = sel.getStartOffset(); - sel = sel.merge({ - anchorKey: startKey, - anchorOffset: startOffset, - focusKey: startKey, - focusOffset: startOffset, - }); - - contentState = contentState.createEntity("IMG", "SEGMENTED", { - src, - triggerModal, - }); - const key = contentState.getLastCreatedEntityKey(); - - // Using insertText so that images behave in an inline fashion - contentState = Modifier.insertText(contentState, sel, " ", null, key); - - editorState = EditorState.push( - editorState, - contentState, - "insert-characters" - ); - - this.setState({ editor: editorState }); - } - - /** - * Inserts a new link at current cursor, or applies to selected text - * @param {String} title Default title to display for the link, if no text is selected in range - * @param {String} href The href - * @param {Boolean} triggerPopup Whether to trigger the popup on creation - */ - insertLink(title, href, triggerPopup) { - let { editor: editorState } = this.state; - let contentState = editorState.getCurrentContent(); - - const sel = editorState.getSelection(); - - contentState = contentState.createEntity("LINK", "MUTABLE", { - href, - triggerPopup, - }); - const key = contentState.getLastCreatedEntityKey(); - - // Selection is a just the cursor, insert new link - if (sel.isCollapsed()) { - // Inserts a space at the cursor, needed so that the user can 'escape' - // from the link entity by clicking after the link, or pressing right arrow - contentState = Modifier.insertText(contentState, sel, " ", null, null); - // Because selection hasn't been updated, this will insert the link *before* - // the newly created space. - contentState = Modifier.insertText(contentState, sel, title, null, key); - - editorState = EditorState.push( - editorState, - contentState, - "insert-characters" - ); - } else { - // Selection is a range, keep the text but make it a link - editorState = RichUtils.toggleLink(editorState, sel, key); - } - - this.setState({ editor: editorState }); - } - - /** - * Toggle an inline text style on/off - * @param {String} styleName Name of the style - * @return {String} The resulting style of the selection - */ - toggleInlineStyle(styleName) { - const { editor } = this.state; - const editorState = RichUtils.toggleInlineStyle(editor, styleName); - this.setState({ editor: editorState }); - return editorState.getCurrentInlineStyle(); - } - - render() { - const { connector, theme } = this.props; - - const st = this.state; - - let containerStyles = styles.container; - if (st.editor.getSelection().getHasFocus()) { - containerStyles += ` ${styles.focused}`; - } - if (theme.container) { - containerStyles += ` ${theme.container}`; - } - - return ( -
this.focus()} - onKeyPress={() => this.focus()} - onFocus={() => this.focus()} - role="button" - tabIndex={0} - > - { - const editorState = RichUtils.handleKeyCommand(state, command); - if (editorState) { - connector.setFocusedEditor(this, editorState); - this.setState({ editor: editorState }); - return true; - } - return false; - }} - onChange={(newState) => { - const p = _.get(this, "props.connector.previewer"); - if (p) p.setVisible(false); - - const hasFocus = newState.getSelection().getHasFocus(); - if ( - !connector.modified && - this.initialContent && - this.initialContent !== newState.getCurrentContent() - ) { - connector.modified = true; - } - connector.setFocusedEditor(hasFocus ? this : null, newState); - this.setState({ editor: newState }); - }} - plugins={[st.markdown ? this.markdownPlugin : {}, this.customPlugin]} - ref={(node) => { - this.node = node; - }} - spellCheck - /> -
- ); - } -} - -EditorWrapper.defaultProps = { - connector: new Connector(), - id: null, - initialContent: null, - theme: {}, -}; - -EditorWrapper.propTypes = { - connector: PT.instanceOf(Connector), - id: PT.string, - initialContent: PT.string, - theme: PT.shape(), -}; diff --git a/src/apps/earn/src/components/Editor/plugin.jsx b/src/apps/earn/src/components/Editor/plugin.jsx deleted file mode 100644 index 2941806cd..000000000 --- a/src/apps/earn/src/components/Editor/plugin.jsx +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Custom DraftJS plugin. - * - * Facilitates the extra functionality required for component. - * Including: Images, Links, and the Note block. - * - * In the future, it should also be possible to validate allowed block elements here, - * and also substitute them for the nearest available option when HTML is rendered. - */ -import _ from "lodash"; -import React from "react"; -import { Map } from "immutable"; -import { EditorState } from "draft-js"; -import { EDITOR_COLOR_MAP } from "../../utils/editor"; - -import Image from "./Image"; -import Link from "./Link"; - -/** - * This is based on the strategy that draft-js-markdown-shortcuts-plugin uses - * so it will work on images and links created with markdown - * - * @param {String} type The block type to create the strategy for, ex. 'IMG', 'LINK' - * @return {Function} The strategy function - */ -const createStrategy = (type) => (contentBlock, callback, contentState) => { - contentBlock.findEntityRanges((metadata) => { - const key = metadata.getEntity(); - return key !== null && contentState.getEntity(key).getType() === type; - }, callback); -}; - -/** - * Creates a custom plugin instance - * - * @param {Object} config Config object, standard interface for Draft JS plugins - * @return {Object} Object representing the Custom Plugin instance, is passed to the - */ -export default ({ editor }) => { - // Store the editor in the closure - const updateEntityData = (key, data) => { - let editorState = editor.state.editor; - - editorState = EditorState.push( - editorState, - editorState.getCurrentContent().replaceEntityData(key, data), - "change-block-data" - ); - - // Force re-render for new data - editorState = EditorState.forceSelection( - editorState, - editorState.getSelection() - ); - - editor.setState({ editorState }); - }; - - const inlineStyles = {}; - - _.forIn(EDITOR_COLOR_MAP, (value, name) => { - inlineStyles[`TEXT_${name}`] = { color: value }; - inlineStyles[`HIGHLIGHT_${name}`] = { background: value }; - }); - - return { - // Provides custom html element rendering for block types - blockRenderMap: Map({ - // draft-js and draft-js-markdown-shortcuts-plugin use inconsistent rendering of - // code-blocks, so we override both of them - "code-block": { - element: "span", - wrapper: , - }, - note: { - element: "span", - wrapper:
, - }, - }), - // Provides custom styling for inline elements (mainly text) - customStyleMap: { - CODE: { - background: "#fafafb", - fontFamily: '"Roboto Mono", monospace', - }, - ...inlineStyles, - }, - // Provides custom component rendering for images and links - decorators: [ - { - strategy: createStrategy("LINK"), - component: Link, - props: { updateEntityData }, - }, - { - strategy: createStrategy("IMG"), - component: Image, - props: { updateEntityData }, - }, - ], - }; -}; diff --git a/src/apps/earn/src/components/Editor/style.scss b/src/apps/earn/src/components/Editor/style.scss deleted file mode 100644 index d3dea4cb3..000000000 --- a/src/apps/earn/src/components/Editor/style.scss +++ /dev/null @@ -1,71 +0,0 @@ -@import "@earn/styles/mixins"; - -:global { - // Has a default z-index of 0 which interferes with component - .DraftEditor-editorContainer { - z-index: auto; - } - - // Draft.js sets some list-related globals that need to be undone, - // and these can't be changed using the blockStyleFn mechanism - .public-DraftStyleDefault-orderedListItem.public-DraftStyleDefault-listLTR::before { - width: auto; - position: relative; - left: 0; - } - - .public-DraftStyleDefault-depth0.public-DraftStyleDefault-listLTR { - margin-left: 0; - } -} - -.container { - @include tc-body-md; - - border: 1px solid $tc-gray-10; - margin: 10px 0; - padding: 10px; - - &:hover { - border: 1px solid $tc-gray-40; - } - - // Draft.js will render code-blocks like
- // This will prevent the code/pre styles from being applied twice - pre { - code { - background: none; - border: 0; - border-radius: none; - margin: 0; - padding: 0; - } - } -} - -.focused { - border: 1px solid $tc-dark-blue; - - &:hover { - border: 1px solid $tc-dark-blue-110; - } -} - -.note { - @include tc-body-sm; - - background: $tc-yellow-30; - border: 1px solid $tc-yellow-70; - border-radius: 6px; - font-style: italic; - color: $tc-black; - line-height: 20px; - padding: 15px 20px; - margin: 25px 0; - - a, - p, - ul { - font-size: 13px; - } -} diff --git a/src/apps/earn/src/components/ErrorMessage/index.jsx b/src/apps/earn/src/components/ErrorMessage/index.jsx deleted file mode 100644 index 6c1eddaba..000000000 --- a/src/apps/earn/src/components/ErrorMessage/index.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useEffect } from "react"; -import PT from "prop-types"; -import { BaseModal, Button } from "~/libs/ui"; - -const ErrorMessage = ({ title, details, onOk }) => { - useEffect(() => { - document.body.classList.add("scrolling-disabled-by-modal"); - - return () => { - document.body.classList.remove("scrolling-disabled-by-modal"); - }; - }, []); - - return ( - { - e.preventDefault(); - onOk(); - }} - > - OK - - )} - > -
- - ); -}; - -ErrorMessage.defaultProps = { - details: "", -}; - -ErrorMessage.propTypes = { - title: PT.string.isRequired, - details: PT.string, - onOk: PT.func.isRequired, -}; - -export default ErrorMessage; diff --git a/src/apps/earn/src/components/ErrorMessage/styles.scss b/src/apps/earn/src/components/ErrorMessage/styles.scss deleted file mode 100644 index 5c7328798..000000000 --- a/src/apps/earn/src/components/ErrorMessage/styles.scss +++ /dev/null @@ -1,49 +0,0 @@ -@import '@earn/styles/variables'; -@import '@earn/styles/mixins'; - -$sm-space-10: $base-unit * 2; -$sm-space-15: $base-unit * 3; -$sm-space-25: $base-unit * 5; -$sm-space-40: $base-unit * 8; -$button-space-32: 6 * $base-unit; - -.container { - @include roboto-regular; - - overflow: hidden; - padding: 8 * $base-unit; - text-align: center; - - @include xs-to-sm { - padding: 40px 10px; - } -} - -.details { - font-weight: 400; - font-size: 13px; - color: $tc-gray-60; - line-height: $sm-space-25; - padding: 0 $sm-space-15; - margin-bottom: $sm-space-10; - text-align: justify; - - a { - color: $tc-dark-blue; - text-decoration: underline; - } -} - -.title { - color: $tc-red; - font-size: 15px; - font-weight: bold; - line-height: $sm-space-25; - margin-bottom: $sm-space-10; - padding: 0 $sm-space-15; - - .id { - color: #000; - font-weight: 500; - } -} diff --git a/src/apps/earn/src/components/GigsRadioButton/index.jsx b/src/apps/earn/src/components/GigsRadioButton/index.jsx deleted file mode 100644 index e7997d2fe..000000000 --- a/src/apps/earn/src/components/GigsRadioButton/index.jsx +++ /dev/null @@ -1,85 +0,0 @@ -/** - * Radio button component. - */ -import { Fragment, useEffect, useRef, useState } from "react"; -import PT from "prop-types"; -import cn from "classnames"; -import _ from "lodash"; -import styles from "./styles.scss"; -import config from "../../config"; - -function RadioButton({ - className, - layout = "", - options, - onChange, - size, - errorMsg, -}) { - const [internalOptions, setInternalOptions] = useState(options); - const optionsWithKey = internalOptions.map((o, oIndex) => ({ - ...o, - key: oIndex, - })); - let sizeStyle = size === "lg" ? "lgSize" : null; - if (!sizeStyle) { - sizeStyle = size === "xs" ? "xsSize" : "smSize"; - } - const delayedOnChange = useRef( - _.debounce((q, cb) => cb(q), config.GUIKIT.DEBOUNCE_ON_CHANGE_TIME) // eslint-disable-line no-undef - ).current; - - useEffect(() => { - setInternalOptions(options); - }, [options]); - - return ( - -
- {optionsWithKey.map((o) => ( -
- -
- ))} -
- {errorMsg ? {errorMsg} : null} -
- ); -} - -RadioButton.defaultProps = { - onChange: () => {}, - size: "sm", - errorMsg: "", -}; - -RadioButton.propTypes = { - options: PT.arrayOf( - PT.shape({ - label: PT.string, - value: PT.bool.isRequired, - }) - ).isRequired, - onChange: PT.func, - size: PT.oneOf(["xs", "sm", "lg"]), - errorMsg: PT.string, -}; - -export default RadioButton; diff --git a/src/apps/earn/src/components/GigsRadioButton/styles.scss b/src/apps/earn/src/components/GigsRadioButton/styles.scss deleted file mode 100644 index 9d1daa675..000000000 --- a/src/apps/earn/src/components/GigsRadioButton/styles.scss +++ /dev/null @@ -1,178 +0,0 @@ -@import "../../styles/variables"; -@import '../../styles/GUIKit/default'; - -/* Create a custom radio button */ -.checkmark { - position: absolute; - top: 0; - left: 0; - background-color: $tc-white; - border-radius: 50%; - border: 1px solid $gui-kit-gray-30; - - /* Create the indicator (the dot/circle - hidden when not checked) */ - &::after { - content: ''; - position: absolute; - display: none; - top: 50%; - left: 50%; - margin-top: -6px; - margin-left: -6px; - width: 12px; - height: 12px; - border-radius: 50%; - background-color: $tc-white; - box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.35); - } - - &.hasError { - border: 2px solid $gui-kit-level-5; - } -} - -.radioButton { - display: flex; - align-items: center; -} - -.label { - font-size: 14px; - cursor: pointer; -} - -/* The container */ -.container { - display: block; - position: relative; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - color: $gui-kit-gray-90; - - /* Hide the browser's default radio button */ - input { - position: absolute; - opacity: 0; - cursor: pointer; - - /* When the radio button is checked, add a blue background */ - &:checked ~ .checkmark { - background-color: $gui-kit-level-2; - box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.29); - border: none; - - /* Show the indicator (dot/circle) when checked */ - &::after { - display: block; - } - } - } -} - -.radioButtonContainer { - display: flex; - flex-direction: column; - - &.horizontal { - flex-direction: row; - - .radioButton { - margin-right: 28px; - - &:last-child { - margin-right: 0; - } - } - } - - .label { - color: $gui-kit-gray-90; - } - - // lg size - &.lgSize { - .container { - padding-left: 24px; - line-height: 24px; - height: 24px; - - .checkmark { - height: 24px; - width: 24px; - - &::after { - margin-top: -6px; - margin-left: -6px; - width: 12px; - height: 12px; - } - } - } - - .label { - margin-left: 8px; - } - } - - // sm size - &.smSize { - .container { - padding-left: 20px; - line-height: 20px; - height: 20px; - - .checkmark { - height: 20px; - width: 20px; - - &::after { - margin-top: -5px; - margin-left: -5px; - width: 10px; - height: 10px; - } - } - } - - .label { - margin-left: 8px; - } - } - - // xs size - &.xsSize { - .container { - padding-left: 15px; - line-height: 15px; - height: 15px; - - .checkmark { - height: 16px; - width: 16px; - - &::after { - margin-top: -4px; - margin-left: -4px; - width: 8px; - height: 8px; - } - } - } - - .label { - margin-left: 8px; - } - } -} - -.errorMessage { - display: block; - - @include errorMessage; - - color: #ef476f; - margin-left: 0; -} diff --git a/src/apps/earn/src/components/LoginModal/index.jsx b/src/apps/earn/src/components/LoginModal/index.jsx deleted file mode 100644 index 6c6557bd0..000000000 --- a/src/apps/earn/src/components/LoginModal/index.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import { useCallback } from "react"; -import MediaQuery from "react-responsive"; -import PT from "prop-types"; -import cn from "classnames"; - -import { Button } from "~/libs/ui"; - -import blobYellow from "../../assets/images/blob-yellow.svg"; -import blobPurple from "../../assets/images/blob-purple.svg"; -import progressBar from "../../assets/images/progress-bar.svg"; -import progressBarMid from "../../assets/images/progress-bar-mid.svg"; -import ProgressBarXS from "../../assets/images/progress-bar-mobile.svg"; -import thinkingFaceMobile from "../../assets/images/thinking-face-mobile.svg"; -import thinkingFace from "../../assets/images/thinking-face-laptop-tablet.svg"; -import { makeLoginUrl, makeRegisterUrl } from "../../utils/url"; -import modalStyles from "../../styles/_modal.scss"; - -import styles from "./styles.scss"; -import BaseModal from "../../../../../libs/ui/lib/components/modals/base-modal/BaseModal"; - -function LoginModal({ onClose, open }) { - const onClickBtnRegister = useCallback(() => { - window.open(makeRegisterUrl(window.location.href)); - }, []); - - return ( - -
- - -

YAY! You are almost done!

-

- Looks like you're not a Topcoder member yet. Or maybe you're - not logged in? - - - - - - - It's quick to register and it's free! -

- - - - - - - - - -
- -
-

- Already a member? Login here -

-
-
- ); -} - -LoginModal.defaultProps = { - utmSource: "gig_listing", -}; - -LoginModal.propTypes = { - utmSource: PT.string, -}; - -export default LoginModal; diff --git a/src/apps/earn/src/components/LoginModal/styles.scss b/src/apps/earn/src/components/LoginModal/styles.scss deleted file mode 100644 index 9e0533fad..000000000 --- a/src/apps/earn/src/components/LoginModal/styles.scss +++ /dev/null @@ -1,156 +0,0 @@ -@import "../../styles/mixins"; - -.title { - position: relative; - color: #9d41c9; - @include barlow-condensed-medium; - font-size: 60px; - line-height: 58px; - text-transform: uppercase; - margin: 0; - margin-bottom: 30px; - - @include xs-to-sm { - max-width: 400px; - margin: auto; - font-size: 60px !important; - line-height: 58px !important; - margin-bottom: 30px; - } - - @media (max-width: 425px) { - max-width: 230px; - margin: auto; - font-size: 36px !important; - line-height: 34px !important; - margin-bottom: 30px; - } -} - -.loginMsg { - color: #2a2a2a; - font-size: 24px; - line-height: 36px; - margin-bottom: 40px; - - img { - display: inline; - } - - @media (max-width: 425px) { - text-align: left !important; - font-size: 20px; - line-height: 30px; - margin-bottom: 30px; - } -} - -.controls { - display: flex; - align-content: center; - justify-content: center; - margin: 0; - - & > button:first-child { - margin-right: 10px !important; - } - - & > a:first-child { - margin-right: 10px !important; - } -} - -.referrals { - display: flex; - overflow: auto; - - .sucessMsg { - font-size: 24px; - line-height: 36px; - margin-bottom: 40px; - } - - .rightAlign { - justify-content: flex-end; - } -} - -.loginRequired { - display: flex; - flex-direction: column; - padding: 80px 55px 40px 60px; - text-align: center; - position: relative; - overflow: hidden; - - @include xs-to-sm { - padding: 50px 35px 40px; - } - - @media (max-width: 425px) { - padding: 50px 35px 70px; - } - - .progressBar { - display: block; - width: 100%; - max-width: 100%; - - @include phone-only { - margin-bottom: 40px; - } - } - - .blobYellow, - .blobPurple { - display: block; - position: absolute; - max-width: 100%; - } - - .blobYellow { - top: 0; - right: 0; - - @media (max-width: 425px) { - max-width: 62px; - } - } - - .blobPurple { - bottom: -5px; - left: 0; - - @media (max-width: 425px) { - max-width: 84px; - bottom: -40px; - } - } - - .thinkingFace { - width: 23px; - margin: 0 5px; - transform: translateY(4px); - - @media (max-width: 425px) { - width: 21px; - transform: translateY(2px); - } - } - - .regTxt { - position: relative; - font-size: 16px; - margin: 30px 0 0; - - a { - color: #0D61BF; - text-decoration: underline; - } - } -} - -.overlay { - background-color: #2a2a2a; - opacity: 0.95; -} diff --git a/src/apps/earn/src/components/Menu/index.jsx b/src/apps/earn/src/components/Menu/index.jsx deleted file mode 100644 index 60fca660b..000000000 --- a/src/apps/earn/src/components/Menu/index.jsx +++ /dev/null @@ -1,182 +0,0 @@ -import React, { useRef, useEffect } from "react"; -import { navigate } from "react-router-dom"; -import PT from "prop-types"; -import _ from "lodash"; - -import IconChevronUp from "../../assets/icons/menu-chevron-up.svg"; -import { MenuSelection, getMenuIcon } from "../../utils"; - -import styles from "./styles.scss"; -import { styled as styledCss } from "@earn/utils"; -const styled = styledCss(styles) - -const Menu = ({ menu, selected, onSelect, isLoggedIn, onUpdateMenu }) => { - const selectionRef = useRef(); - if (!selectionRef.current) { - selectionRef.current = new MenuSelection( - _.cloneDeep(menu), - selected, - onSelect, - onUpdateMenu - ); - } - - useEffect(() => { - selectionRef.current.setMenu(menu); - }, [menu]); - - useEffect(() => { - selectionRef.current.select(selected); - }, [selected]); - - // useEffect(() => { - // if (selectionRef.current.isAuth(selected) && isLoggedIn === false) { - // utils.auth.logIn(); - // } - // }, [selected, isLoggedIn]); - - const onSelectMenuItem = (name, path) => { - selectionRef.current.select(name); - if (path) { - navigate(path); - } - }; - - const getIcon = (menuItem, active) => { - const name = active ? menuItem.iconActive : menuItem.icon; - return getMenuIcon(name); - }; - - const isExpandable = (menuItem) => - selectionRef.current.isExpandable(menuItem); - const isSelected = (menuItem) => selectionRef.current.isSelected(menuItem); - const isExpanded = (menuItem) => selectionRef.current.isExpanded(menuItem); - const isActive = (menuItem) => selectionRef.current.isActive(menuItem); - - const renderSubSubmenu = (subMenuItem) => { - return ( -
    - {subMenuItem.children.map((subSubmenuItem) => ( -
  • - { - onSelectMenuItem(subSubmenuItem.name, subSubmenuItem.path); - }} - > - {subSubmenuItem.name} - -
  • - ))} -
- ); - }; - - const renderSubmenu = (menuItem) => { - if (!menuItem.children) { - return null; - } - - return ( -
    - {menuItem.children.map((subMenuItem) => ( -
  • - { - onSelectMenuItem( - subMenuItem.name, - isExpandable(subMenuItem) ? null : subMenuItem.path - ); - }} - > - {subMenuItem.name} - - {isExpandable(subMenuItem) && renderSubSubmenu(subMenuItem)} -
  • - ))} -
- ); - }; - - return ( - - ); -}; - -Menu.propTypes = { - menu: PT.shape(), - selected: PT.string, - onSelect: PT.func, - isLoggedIn: PT.oneOf([null, true, false]), -}; - -export default Menu; diff --git a/src/apps/earn/src/components/Menu/styles.scss b/src/apps/earn/src/components/Menu/styles.scss deleted file mode 100644 index 3782a14a4..000000000 --- a/src/apps/earn/src/components/Menu/styles.scss +++ /dev/null @@ -1,102 +0,0 @@ -@import "@earn/styles/variables"; - -$menu-padding-x: 4 * $base-unit; -$menu-padding-y: 20px; - -.menu { - padding: $menu-padding-y $menu-padding-x (3 * $base-unit); - - &.logged-in {} - - &.logged-out { - .menu-item-auth { - display: none; - } - } -} - -.menu-item { - padding: 4px 0; - cursor: pointer; - - .link { - display: flex; - align-items: center; - line-height: 26px; - outline: none; - } - - .icon { - width: 24px; - height: 24px; - margin-right: 16px; - text-align: left; - } - - .text {} - - .arrow { - width: 21px; - height: 21px; - margin-left: 8px; - line-height: 1; - text-align: center; - vertical-align: middle; - - &.up {} - &.down { - transform: rotate(180deg); - } - } - - &.active > .link { - font-weight: 500; - } - - &.selected > .link { - color: $tc-turquoise-dark1; - } -} - -.menu-item-main > .link { - margin-left: -20px; - margin-right: -20px; - padding-left: 20px; - padding-right: 20px; -} - -.menu-item-main.active > .link { - box-shadow: inset 4px 0 $tc-turquoise; -} - -.menu-item-main > .link + ul, -.menu-item-secondary > .link + ul { - display: none; -} - -.menu-item-main.expanded > .link + ul, -.menu-item-secondary.expanded > .link + ul { - display: block; - cursor: default; -} - -.menu-item-secondary.active.collapsed { - color: $tc-turquoise-dark1; -} - -.sub-menu { - padding-left: 24px + 16px; - - .menu-item { - cursor: default; - .link { - cursor: pointer; - display: inline-block; - } - } - -} - -.sub-submenu { - padding-left: 20px; -} diff --git a/src/apps/earn/src/components/MetaTags/index.jsx b/src/apps/earn/src/components/MetaTags/index.jsx deleted file mode 100644 index fd60501f3..000000000 --- a/src/apps/earn/src/components/MetaTags/index.jsx +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Auxiliary wrapper around React Helmet that helps to generate meta tags for - * generic use cases. - * - * NOTE: This component relies on `domain` path of Redux store to hold - * the current app domain, which will serve as the base path for the bundled - * images. - */ - -import React from "react"; -import PT from "prop-types"; -import { connect } from "react-redux"; -import { Helmet } from "react-helmet"; - -function MetaTags({ - description, - domain, - image, - siteName, - socialDescription, - socialTitle, - title, - url, -}) { - const img = `${domain}${image}`; - const socTitle = socialTitle || title; - const socDesc = socialDescription || description; - return ( - - {/* General tags. */} - {title} - - - {/* Twitter cards. */} - - - - {image ? : null} - {siteName ? : null} - - {/* Open Graph data. */} - - {image ? : null} - {image ? : null} - - {siteName ? : null} - {url ? : null} - - ); -} - -MetaTags.defaultProps = { - image: null, - siteName: null, - socialDescription: null, - socialTitle: null, - url: null, -}; - -MetaTags.propTypes = { - description: PT.string.isRequired, - domain: PT.string, - image: PT.string, - siteName: PT.string, - socialDescription: PT.string, - socialTitle: PT.string, - title: PT.string.isRequired, - url: PT.string, -}; - -/* TODO: It is not good to depend on the domain written into redux state here, - * better pass it via the renderer context at the server side, and get it from - * the location at the frontend side, or something similar? */ -export default connect((state) => ({ domain: state.domain }))(MetaTags); diff --git a/src/apps/earn/src/components/MultiSelect/index.jsx b/src/apps/earn/src/components/MultiSelect/index.jsx deleted file mode 100644 index 161147612..000000000 --- a/src/apps/earn/src/components/MultiSelect/index.jsx +++ /dev/null @@ -1,237 +0,0 @@ -//import "react-select/dist/react-select.css"; -import styles from './styles.scss'; -import React, { useState } from 'react'; -import PT from 'prop-types'; -import Select, { components } from 'react-select'; -import cn from 'classnames'; -import iconDown from '../../assets/icons/dropdown-arrow.png'; - -const Arrow = () => ( - icon down -); - -const Menu = (props) => { - return ( - - {props.children} - - ); -}; -const MenuList = (props) => { - return ( - - {props.children} - - ); -}; - -const CustomOption = (props) => { - return ( - - {props.children} - - ); -}; - -const ValueContainer = ({ children, ...props }) => ( - - {children} - -); - -const ControlComponent = (props) => { - return ( - - ); -}; -const IndicatorsContainer = (props) => { - return ( - - ); -}; -const SelectContainer = (props) => { - return ( - - ); -}; - -const MultiValueContainer = (props) => { - return ( - - ); -}; - -const MultiValueLabel = (props) => { - return ( - - ); -}; - -const MultiValueRemove = ({ children, ...props }) => ( - - {children} - -); - -const Input = (props) => { - return ( - - ); -}; - -const Placeholder = (props) => { - return ( - - ); -}; - -const IndicatorSeparator = () => { - return null; -}; -const DropdownIndicator = () => { - return null; -}; - -/** - * Displays a multi-select field. - * - * @param {Object} props component properties - * @returns {JSX.Element} - */ -const MultiSelect = ({ - className, - clearable, - label, - isRequired = false, - onChange, - onFocus, - options, - optLabelKey, - optValueKey, - placeholder, - showArrow = false, - size, - value, - error, -}) => { - const [focused, setFocused] = useState(false); - return ( - <> -
setFocused(true)} - onBlurCapture={() => setFocused(false)} - className={[ - styles.container, - styles[size], - className, - !!error ? styles['hasError'] : '', - ].join(' ')} - > - {label && ( - - {label + (isRequired ? ' *' : '')} - - )} - { - const newOptions = optionsWithKey.map((oWithKeyTmp) => ({ - label: oWithKeyTmp.label, - value: o.key === oWithKeyTmp.key, - })); - setInternalOptions(newOptions); - delayedOnChange(_.cloneDeep(newOptions), onChange); - }} - /> - - {o.label ? {o.label} : null} - -
- ))} -
- {errorMsg ? {errorMsg} : null} - - ); -} - -RadioButton.defaultProps = { - onChange: () => {}, - size: "sm", - errorMsg: "", -}; - -RadioButton.propTypes = { - options: PT.arrayOf( - PT.shape({ - label: PT.string, - value: PT.bool.isRequired, - }) - ).isRequired, - onChange: PT.func, - size: PT.oneOf(["xs", "sm", "lg"]), - errorMsg: PT.string, -}; - -export default RadioButton; diff --git a/src/apps/earn/src/components/RadioButton/styles.module.scss b/src/apps/earn/src/components/RadioButton/styles.module.scss deleted file mode 100644 index 0f8fa0bd5..000000000 --- a/src/apps/earn/src/components/RadioButton/styles.module.scss +++ /dev/null @@ -1,166 +0,0 @@ -@import "@earn/styles/variables"; -@import '@earn/styles/GUIKit/default'; - -/* Create a custom radio button */ -.checkmark { - position: absolute; - top: 0; - left: 0; - background-color: $tc-white; - border-radius: 50%; - border: 1px solid $gui-kit-gray-30; - - /* Create the indicator (the dot/circle - hidden when not checked) */ - &::after { - content: ''; - position: absolute; - display: none; - top: 50%; - left: 50%; - margin-top: -6px; - margin-left: -6px; - width: 12px; - height: 12px; - border-radius: 50%; - background-color: $tc-white; - box-shadow: 0 1px 1px 0 rgba(0, 0, 0, 0.35); - } - - &.hasError { - border: 2px solid $gui-kit-level-5; - } -} - -.radioButton { - display: flex; - align-items: center; -} - -.label { - font-size: 14px; - cursor: pointer; -} - -/* The container */ -.container { - display: block; - position: relative; - cursor: pointer; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - color: $gui-kit-gray-90; - - /* Hide the browser's default radio button */ - input { - position: absolute; - opacity: 0; - cursor: pointer; - - /* When the radio button is checked, add a blue background */ - &:checked ~ .checkmark { - background-color: $gui-kit-level-2; - box-shadow: inset 0 1px 2px 0 rgba(0, 0, 0, 0.29); - border: none; - - /* Show the indicator (dot/circle) when checked */ - &::after { - display: block; - } - } - } -} - -.radioButtonContainer { - display: flex; - flex-direction: column; - - .label { - color: $gui-kit-gray-90; - } - - // lg size - &.lgSize { - .container { - padding-left: 24px; - line-height: 24px; - height: 24px; - - .checkmark { - height: 24px; - width: 24px; - - &::after { - margin-top: -6px; - margin-left: -6px; - width: 12px; - height: 12px; - } - } - } - - .label { - margin-left: 8px; - } - } - - // sm size - &.smSize { - .container { - padding-left: 20px; - line-height: 20px; - height: 20px; - - .checkmark { - height: 20px; - width: 20px; - - &::after { - margin-top: -5px; - margin-left: -5px; - width: 10px; - height: 10px; - } - } - } - - .label { - margin-left: 8px; - } - } - - // xs size - &.xsSize { - .container { - padding-left: 15px; - line-height: 15px; - height: 15px; - - .checkmark { - height: 16px; - width: 16px; - - &::after { - margin-top: -4px; - margin-left: -4px; - width: 8px; - height: 8px; - } - } - } - - .label { - margin-left: 8px; - } - } -} - -.errorMessage { - display: block; - - @include errorMessage; - - color: #ef476f; - margin-left: 0; -} diff --git a/src/apps/earn/src/components/ReferralAuthModal/index.jsx b/src/apps/earn/src/components/ReferralAuthModal/index.jsx deleted file mode 100644 index 063726d7e..000000000 --- a/src/apps/earn/src/components/ReferralAuthModal/index.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import { useCallback } from "react"; -import PT from "prop-types"; - -import { BaseModal, Button } from "~/libs/ui"; - -import { REFERRAL_PROGRAM_URL } from "../../constants"; -import { makeLoginUrl, makeRegisterUrl } from "../../utils/url"; - -import styles from "./styles.scss"; - -/** - * Displays a modal with "Login" and "Register" buttons which is displayed after - * the user clicks the button in the ReferralBanner. - * - * @param {Object} props component properties - * @returns {JSX.Element} - */ -const ReferralAuthModal = ({ onClose, open }) => { - const onClickBtnLogin = useCallback(() => { - window.location = makeLoginUrl(window.location.href); - }, []); - - const onClickBtnRegister = useCallback(() => { - window.open(makeRegisterUrl(window.location.href)); - }, []); - - return ( - -
Referral Program
-
Please login to receive your referral code.
-
- - -
-
- Find out how the referral program works{" "} - - here - - . -
-
- ); -}; - -ReferralAuthModal.propTypes = { - onClose: PT.func.isRequired, - open: PT.bool.isRequired, -}; - -export default ReferralAuthModal; diff --git a/src/apps/earn/src/components/ReferralAuthModal/styles.scss b/src/apps/earn/src/components/ReferralAuthModal/styles.scss deleted file mode 100644 index a9c23b93b..000000000 --- a/src/apps/earn/src/components/ReferralAuthModal/styles.scss +++ /dev/null @@ -1,48 +0,0 @@ -@import '@libs/ui/styles/includes'; -@import "../../styles/mixins"; -@import "../../styles/variables"; - -.content { - display: block; -} - -.title { - @include barlow-condensed-medium; - font-size: 34px; - line-height: 38px; - text-transform: uppercase; - color: #1e94a3; -} - -.message { - margin: 20px 0 0; - font-size: 20px; - line-height: 30px; - text-align: center; - - @include tablet { - font-size: 24px; - line-height: 36px; - } -} - -.controls { - display: flex; - justify-content: center; - margin: 40px 0 0; - gap: $sp-3; -} - -.hint { - margin: 10px 0 0; - @include roboto-regular; - font-size: 14px; - line-height: 26px; - - a { - font-size: 16px; - line-height: 24px; - color: #0d61bf; - text-decoration: underline; - } -} diff --git a/src/apps/earn/src/components/ReferralEmailModal/index.jsx b/src/apps/earn/src/components/ReferralEmailModal/index.jsx deleted file mode 100644 index 065c75aa4..000000000 --- a/src/apps/earn/src/components/ReferralEmailModal/index.jsx +++ /dev/null @@ -1,68 +0,0 @@ -import modalStyles from "../../styles/_modal.scss"; -import cn from "classnames"; - -import { BaseModal, Button, LinkButton } from "~/libs/ui"; - -import { LoadingCircles } from "~/libs/ui"; -import { GIG_LIST_ROUTE } from "../../constants"; - -import styles from "./styles.scss"; - -const ReferralEmailModal = ({ error, isBusy, isUserError, onClose, open }) => ( - - {isBusy ? ( - <> -
Sending your referral...
- - - ) : ( - <> -
- {error ? "Oops!" : "Congratulations!"} -
-
- {error ? error : "Your referral has been sent."} -
- {!!error && - (isUserError ? ( -
- If you think this is an error please contact -
- support@topcoder.com. -
- ) : ( -
- Looks like there is a problem on our end. Please try again. -
- If this persists please contact{" "} - support@topcoder.com. -
- ))} -
- - - FIND ANOTHER GIG - -
- - )} -
-); - -export default ReferralEmailModal; diff --git a/src/apps/earn/src/components/ReferralEmailModal/styles.scss b/src/apps/earn/src/components/ReferralEmailModal/styles.scss deleted file mode 100644 index c3df4a43b..000000000 --- a/src/apps/earn/src/components/ReferralEmailModal/styles.scss +++ /dev/null @@ -1,24 +0,0 @@ -.sendLoadingIndicator { - display: block; - margin: 20px auto 0; -} - -.hint { - margin: 40px 0 20px; - font-size: 14px; - line-height: 26px; - text-align: center; - - a { - color: #0d61bf; - text-decoration: underline; - } - - + .controls { - margin: 0; - } -} - -.error { - color: #f00; -} diff --git a/src/apps/earn/src/components/SearchField/index.jsx b/src/apps/earn/src/components/SearchField/index.jsx deleted file mode 100644 index 7f58ca0bc..000000000 --- a/src/apps/earn/src/components/SearchField/index.jsx +++ /dev/null @@ -1,63 +0,0 @@ -import styles from "./styles.scss"; -import React, { useCallback } from "react"; -import PT from "prop-types"; -import cn from "classnames"; - -import { ReactComponent as IconMagnifier } from "../../assets/icons/icon-magnifier.svg"; - -/** - * Displays search input field. - * - * @param {Object} props component properties - * @param {string} [props.className] class name added to root element - * @param {string} props.id id for input element - * @param {string} props.placeholder placeholder text - * @param {string} props.name name for input element - * @param {'medium'|'small'} [props.size] field size - * @param {function} props.onChange function called when input value changes - * @param {string} props.value input value - * @returns {JSX.Element} - */ -const SearchField = ({ - className, - id, - name, - size = "medium", - onChange, - placeholder, - value, -}) => { - const onInputChange = useCallback( - (event) => { - onChange(event.target.value); - }, - [onChange] - ); - - return ( -
- - -
- ); -}; - -SearchField.propTypes = { - className: PT.string, - id: PT.string.isRequired, - size: PT.oneOf(["medium", "small"]), - name: PT.string.isRequired, - onChange: PT.func.isRequired, - placeholder: PT.string, - value: PT.oneOfType([PT.number, PT.string]), -}; - -export default SearchField; diff --git a/src/apps/earn/src/components/SearchField/styles.scss b/src/apps/earn/src/components/SearchField/styles.scss deleted file mode 100644 index 8460e83c1..000000000 --- a/src/apps/earn/src/components/SearchField/styles.scss +++ /dev/null @@ -1,38 +0,0 @@ -.container { - display: flex; - align-items: center; - border: 1px solid #aaa; - border-radius: 6px; - background-color: #fff; - - &.medium { - height: 40px; - } - - &.small { - height: 30px; - } -} - -.icon { - margin: auto 10px; - width: 16px; - height: 16px; -} - -input.input { - flex: 1 1 0; - margin: 0; - border: none !important; - padding: 8px 16px 8px 0; - height: 22px; - line-height: 22px; - background: none; - outline: none !important; - box-shadow: none !important; - - &::placeholder { - text-transform: none; - color: #aaa; - } -} diff --git a/src/apps/earn/src/components/Select/index.jsx b/src/apps/earn/src/components/Select/index.jsx deleted file mode 100644 index c0e8a5e25..000000000 --- a/src/apps/earn/src/components/Select/index.jsx +++ /dev/null @@ -1,21 +0,0 @@ -import _ from "lodash"; -import ReactSelect from "react-select"; -import PT from "prop-types"; -import styles from "./style.scss"; - -export default function Select(props) { - const { selectRef } = props; - return ( -
- -
- ); -} - -Select.defaultProps = { - selectRef: _.noop, -}; - -Select.propTypes = { - selectRef: PT.func, -}; diff --git a/src/apps/earn/src/components/Select/style.scss b/src/apps/earn/src/components/Select/style.scss deleted file mode 100644 index f823c926c..000000000 --- a/src/apps/earn/src/components/Select/style.scss +++ /dev/null @@ -1,18 +0,0 @@ -.select { - :global { - // @import "react-select/dist/react-select"; - - width: 100%; - - input.Select-input, - input.Select-input:focus { - background-color: transparent !important; - margin-left: 0 !important; - padding-right: 6px !important; - } - - .Select-multi-value-wrapper { - width: 100% !important; - } - } -} diff --git a/src/apps/earn/src/components/SubmissionManagement/Icons/IconCloudDownload.svg b/src/apps/earn/src/components/SubmissionManagement/Icons/IconCloudDownload.svg deleted file mode 100644 index b17e3bc52..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/Icons/IconCloudDownload.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file diff --git a/src/apps/earn/src/components/SubmissionManagement/Icons/IconMinimalDown.svg b/src/apps/earn/src/components/SubmissionManagement/Icons/IconMinimalDown.svg deleted file mode 100644 index 96fdc951e..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/Icons/IconMinimalDown.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/components/SubmissionManagement/Icons/IconMinimalLeft.svg b/src/apps/earn/src/components/SubmissionManagement/Icons/IconMinimalLeft.svg deleted file mode 100644 index 287bf690e..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/Icons/IconMinimalLeft.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/src/apps/earn/src/components/SubmissionManagement/Icons/IconMinimalRight.svg b/src/apps/earn/src/components/SubmissionManagement/Icons/IconMinimalRight.svg deleted file mode 100644 index d2905c94b..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/Icons/IconMinimalRight.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/src/apps/earn/src/components/SubmissionManagement/Icons/IconMinimalUp.svg b/src/apps/earn/src/components/SubmissionManagement/Icons/IconMinimalUp.svg deleted file mode 100644 index 9bf3e2d5a..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/Icons/IconMinimalUp.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - diff --git a/src/apps/earn/src/components/SubmissionManagement/Icons/IconSearch.svg b/src/apps/earn/src/components/SubmissionManagement/Icons/IconSearch.svg deleted file mode 100644 index 30f678532..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/Icons/IconSearch.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/components/SubmissionManagement/Icons/IconShare.svg b/src/apps/earn/src/components/SubmissionManagement/Icons/IconShare.svg deleted file mode 100644 index 2c731ab66..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/Icons/IconShare.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - diff --git a/src/apps/earn/src/components/SubmissionManagement/Icons/IconSquareDownload.svg b/src/apps/earn/src/components/SubmissionManagement/Icons/IconSquareDownload.svg deleted file mode 100644 index e56fda655..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/Icons/IconSquareDownload.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/components/SubmissionManagement/Icons/IconTrashSimple.svg b/src/apps/earn/src/components/SubmissionManagement/Icons/IconTrashSimple.svg deleted file mode 100644 index 5b430a5e3..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/Icons/IconTrashSimple.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - diff --git a/src/apps/earn/src/components/SubmissionManagement/Icons/IconZoom.svg b/src/apps/earn/src/components/SubmissionManagement/Icons/IconZoom.svg deleted file mode 100644 index 30f678532..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/Icons/IconZoom.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/apps/earn/src/components/SubmissionManagement/ScreeningDetails/index.jsx b/src/apps/earn/src/components/SubmissionManagement/ScreeningDetails/index.jsx deleted file mode 100644 index 706ff51f9..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/ScreeningDetails/index.jsx +++ /dev/null @@ -1,133 +0,0 @@ -/** - * This compnent receives via props the screening object and nicely renders them. - * Note that this component both has the text coming directly from the screening object, - * and the text generated based on the screening status - * - */ - -import PT from 'prop-types'; -import shortid from 'shortid'; -import { styled as styledCss } from '@earn/utils'; - -import styles from './styles.scss'; -const styled = styledCss(styles); - -export default function ScreeningDetails(props) { - const { - screeningObject, - helpPageUrl, - } = props; - - const hasWarnings = screeningObject.warnings; - const hasStatus = screeningObject.status; - const hasStatusPassed = hasStatus === 'passed'; - const hasStatusFailed = hasStatus === 'failed'; - const hasPending = screeningObject.status === 'pending'; - const warnLength = screeningObject.warnings && hasWarnings.length; - - const setStatusInfo = () => { - if (hasPending) { - return { - title: 'Pending', - classname: 'pending', - message: 'Your submission has been received, and will be screened after the end of the phase', - }; - } if (hasStatusPassed && !hasWarnings) { - return { - title: 'Passed Screening', - classname: 'passed', - message: 'You have passed screening.', - }; - } if (hasStatusFailed && !hasWarnings) { - return { - title: 'Failed Screening', - classname: 'failed', - message: 'You have failed screening', - }; - } if (hasStatusPassed && hasWarnings) { - return { - title: 'Passed Screening with Warnings', - classname: 'passed', - message: `You have passed screening, but the screener has given you ${warnLength} warnings that you must fix in round 2.`, - }; - } if (hasStatusFailed && hasWarnings) { - return { - title: 'Failed Screening with Warnings', - classname: 'failed', - message: 'You have failed screening and the screener has given you the following warning.', - }; - } - return { - title: '', - classname: '', - message: 'Your submission has been received, and will be evaluated during Review phase.', - }; - }; - - let warnings = []; - if (screeningObject.warnings) { - warnings = screeningObject.warnings.map((warning, i) => ( -
-
- - Warning - - {' '} - {`${1 + i} : ${warning.brief}`} -
-

- {warning.details} -

-
- )); - } - return ( -
-
-

- {setStatusInfo().title} -

- {/* - NOTE: TonyJ asked to remove the OR links from the page to keep - users within the new Topcoder site as much as we can. Not wiping - out the code just in case we decide to bring it back later. - - Online Review - - */} -
-

- {setStatusInfo().message} - - {' '}Need help? - -

-
- {warnings} - {((hasStatusFailed) || (hasStatusPassed && hasWarnings)) - && ( -

- Need more info on how to pass screening? - Go to help to read Rules & Policies. -

- )} -
-
- ); -} - -ScreeningDetails.defaultProps = { - screeningObject: {}, - helpPageUrl: '', -}; - -ScreeningDetails.propTypes = { - screeningObject: PT.shape({ - status: PT.string, - warnings: PT.arrayOf(PT.shape({ - brief: PT.string.isRequired, - details: PT.string.isRequired, - })), - }), - helpPageUrl: PT.string, -}; diff --git a/src/apps/earn/src/components/SubmissionManagement/ScreeningDetails/styles.scss b/src/apps/earn/src/components/SubmissionManagement/ScreeningDetails/styles.scss deleted file mode 100644 index 2310fef9b..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/ScreeningDetails/styles.scss +++ /dev/null @@ -1,88 +0,0 @@ -@import '@earn/styles/mixins'; -$status-space-10: $base-unit * 2; -$status-space-15: $base-unit * 3; -$status-space-20: $base-unit * 4; -$status-space-25: $base-unit * 5; -$gray-color: $tc-gray-80; -$green-color: $tc-green; -$red-color: $tc-red; - -.online-review-link { - background: none; -} - -.screening-details { - font-weight: 500; - font-size: 14px; - color: $tc-black; - letter-spacing: 0; - line-height: 22px; - - .screening-details-head { - display: flex; - margin-bottom: $base-unit; - - .status-title { - font-weight: 700; - color: $tc-orange; - letter-spacing: 0; - line-height: $status-space-20; - - .passed { - color: $green-color; - } - - &.failed { - color: $red-color; - } - - &.pending { - color: $gray-color; - } - } - - .online-review-link { - display: block; - margin-left: auto; - line-height: $status-space-20; - font-weight: 400; - font-size: 13px; - color: $tc-dark-blue-110; - text-decoration: underline; - padding: 0; - border: none; - text-transform: capitalize; - - &:hover { - opacity: 0.7; - } - - &:focus { - outline: none; - } - } - } - - .screening-warning { - margin-top: $status-space-15; - } - - .more-info { - margin-top: $status-space-15; - margin-bottom: $base-unit; - } - - .warning-bold { - font-weight: 700; - line-height: $status-space-20; - } - - .help-btn { - text-align: right; - } - - .help-link { - color: $tc-dark-blue-110; - cursor: pointer; - } -} diff --git a/src/apps/earn/src/components/SubmissionManagement/ScreeningStatus/index.jsx b/src/apps/earn/src/components/SubmissionManagement/ScreeningStatus/index.jsx deleted file mode 100644 index 3110f371e..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/ScreeningStatus/index.jsx +++ /dev/null @@ -1,97 +0,0 @@ -/** - * This component receives the screening object via props. - * Depending on status, presence and count of warnings, - * it renders itself in one of the ways shown in specs. - * When status is pending it renders just 'Not yet performed' text. - * When no screening object received, nothing is rendered. - * - * When hovered this object should update mouse cursor to pointer, - * and on click it should call the onClick() callback passed from parent. - * Parent code will use that signal to show/hide the panel with - * screening details for this submission. - * - */ - -import _ from 'lodash'; -import React from 'react'; -import PT from 'prop-types'; - -import { styled as styledCss } from '@earn/utils'; - -import styles from './styles.scss'; -const styled = styledCss(styles); - -export default function ScreeningStatus(props) { - const { - screeningObject, - onShowDetails, - submissionId, - } = props; - - const hasWarnings = screeningObject.warnings; - const hasStatus = screeningObject.status; - const hasStatusPassed = hasStatus === 'passed'; - const hasStatusFailed = hasStatus === 'failed'; - const hasPending = screeningObject.status === 'pending'; - const warnLength = screeningObject.warnings && hasWarnings.length; - - const setClassName = () => { - if (hasPending) { - return 'pending'; - } if (hasStatusPassed && !hasWarnings) { - return 'pass-with-no-warn'; - } if (hasStatusFailed && !hasWarnings) { - return 'fail-with-no-warn'; - } - return 'has-warn'; - }; - const setStatusClassName = () => { - if (hasStatusPassed && hasWarnings) { - return 'passed'; - } if (hasStatusFailed && hasWarnings) { - return 'failed'; - } - return ''; - }; - return ( - - ); -} - -ScreeningStatus.defaultProps = { - onShowDetails: _.noop, -}; - -ScreeningStatus.propTypes = { - screeningObject: PT.shape({ - status: PT.string, - warnings: PT.array, - }).isRequired, - onShowDetails: PT.func, - submissionId: PT.number.isRequired, -}; diff --git a/src/apps/earn/src/components/SubmissionManagement/ScreeningStatus/styles.scss b/src/apps/earn/src/components/SubmissionManagement/ScreeningStatus/styles.scss deleted file mode 100644 index 80fa75a06..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/ScreeningStatus/styles.scss +++ /dev/null @@ -1,68 +0,0 @@ -@import '@earn/styles/mixins'; -$status-space-10: $base-unit * 2; -$status-space-20: $base-unit * 4; -$status-space-40: $base-unit * 8; -$gray-color: #a3a3ad; -$green-color: #60c700; -$red-color: #f22f24; - -.status { - font-weight: 700; - display: inline-block; - padding: $base-unit $status-space-10; - border-radius: $status-space-40 0 0 $status-space-40; - text-transform: capitalize; - - &.passed { - background: $tc-orange; - } - - &.failed { - background: $red-color; - } -} - -.screening-status { - background: $tc-gray-50; - border-radius: $status-space-40; - font-weight: 400; - font-size: 13px; - line-height: $status-space-20; - color: $tc-white; - padding: $base-unit $status-space-10; - display: inline-block; - text-transform: initial; - cursor: pointer; - - &.has-warn { - padding: 0 $status-space-10 0 0; - } - - &.pass-with-no-warn { - background: $green-color; - - .status { - padding: 0 $base-unit; - } - } - - &.fail-with-no-warn { - background: $red-color; - - .status { - padding: 0 $base-unit; - } - } - - &.pending { - background: transparent; - font-size: 15px; - color: $gray-color; - font-style: italic; - } -} - -.warning { - padding-left: $status-space-10; - display: inline-block; -} diff --git a/src/apps/earn/src/components/SubmissionManagement/Submission/index.jsx b/src/apps/earn/src/components/SubmissionManagement/Submission/index.jsx deleted file mode 100644 index 367db65b9..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/Submission/index.jsx +++ /dev/null @@ -1,145 +0,0 @@ -/** - * This component receives via props a single submission data object, - * and showScreeningDetails boolean property, which should tell whether - * the Screening Details component should be rendered or not - * (and also to choose the proper orientation of arrow icon). - * - * Also, this component will receive the following callbacks to be triggered - * when user clicks on buttons/icons/links: - * onDelete() (to be triggered by delete icon), - * onDownload() (to be triggered by download icon), - * onShowDetails() (to be triggered by details arrow icon, and also by screening status component). - */ - -import _ from 'lodash'; -import moment from 'moment'; -import { COMPETITION_TRACKS, CHALLENGE_STATUS, safeForDownload } from '@earn/utils/tc'; - -import PT from 'prop-types'; - -import { ReactComponent as DeleteIcon } from '../Icons/IconTrashSimple.svg'; -import { ReactComponent as DownloadIcon } from '../Icons/IconSquareDownload.svg'; -import { ReactComponent as ExpandIcon } from '../Icons/IconMinimalDown.svg'; -import ScreeningStatus from '../ScreeningStatus'; -import { styled as styledCss } from '@earn/utils'; - -import styles from './styles.scss'; -const styled = styledCss(styles); - -export default function Submission(props) { - const { - submissionObject, - showScreeningDetails, - track, - onDownload, - onDelete, - onShowDetails, - status, - allowDelete, - } = props; - const formatDate = date => moment(+new Date(date)).format('MMM DD, YYYY hh:mm A'); - const onDownloadSubmission = onDownload.bind(1, submissionObject.id); - const safeForDownloadCheck = safeForDownload(submissionObject.url); - - return ( - - - ID - {submissionObject.legacySubmissionId} -
{submissionObject.id}
- - - TYPE - {submissionObject.type} - - - Submission Date - {formatDate(submissionObject.created)} - - { - track === COMPETITION_TRACKS.DES && ( - - Screening Status - {safeForDownloadCheck !== true ? safeForDownloadCheck : submissionObject.screening - && ( - - )} - - ) - } - -
- - { /* - TODO: At the moment we just fetch downloads from the legacy - Topcoder Studio API, and we don't need any JS code to this. - It may change soon, as we move to the new TC API for - downloads. Then we'll use this commented out code or - remove it for good. - - */ } - {status !== CHALLENGE_STATUS.COMPLETED - && track === COMPETITION_TRACKS.DES - && safeForDownloadCheck === true && ( - - ) - } - -
- - - ); -} - -Submission.defaultProps = { - submissionObject: {}, - showScreeningDetails: false, - onShowDetails: _.noop, -}; - -Submission.propTypes = { - submissionObject: PT.shape({ - id: PT.string, - legacySubmissionId: PT.string, - warpreviewnings: PT.string, - screening: PT.shape({ - status: PT.string, - }), - submitted: PT.string, - type: PT.string, - created: PT.any, - download: PT.any, - url: PT.string, - }), - showScreeningDetails: PT.bool, - track: PT.string.isRequired, - onDownload: PT.func.isRequired, - onDelete: PT.func.isRequired, - onShowDetails: PT.func, - status: PT.string.isRequired, - allowDelete: PT.bool.isRequired, -}; diff --git a/src/apps/earn/src/components/SubmissionManagement/Submission/styles.scss b/src/apps/earn/src/components/SubmissionManagement/Submission/styles.scss deleted file mode 100644 index 3511c9e26..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/Submission/styles.scss +++ /dev/null @@ -1,202 +0,0 @@ -@import '@earn/styles/mixins'; -$submission-space-10: $base-unit * 2; -$submission-space-20: $base-unit * 4; -$submission-space-25: $base-unit * 5; -$submission-space-50: $base-unit * 10; - -.submission-row { - width: 100%; - font-size: 15px; - color: $tc-black; - font-weight: 400; - - @include xs-to-sm { - display: block; - position: relative; - padding: 10px 0; - } - - td { - // vertical-align: middle; - padding: 16px; - background: $tc-white; - border-top: 1px solid $tc-gray-10; - color: $tc-black; - font-weight: 500; - font-size: 14px; - line-height: 22px; - - .mobile-header { - display: none; - - @include xs-to-md { - display: block; - color: #767676; - font-size: 11px; - line-height: 14px; - text-transform: uppercase; - text-align: left; - justify-content: flex-start; - } - } - - @include xs-to-lg { - padding: $submission-space-10; - } - - @include xs-to-sm { - display: block; - border: none; - } - - &.no-submission { - line-height: $submission-space-20; - padding: $submission-space-50 $submission-space-20; - text-align: center; - } - - &.dev-details { - padding: 16px; - - @include xs-to-md { - padding: 0 10px; - } - } - } - - .preview-col { - @include xs-to-sm { - float: left; - } - - .design-img { - width: 90px; - height: 90px; - - @include xs-to-sm { - width: 80px; - height: 80px; - } - } - - .dev-img { - width: 40px; - height: 40px; - } - } - - .id-col { - font-weight: 700; - width: 32%; - - @include xs-to-sm { - width: 80%; - } - } - - .type-col { - width: 21%; - - @include xs-to-sm { - width: 80%; - } - } - - .date-col { - width: 20%; - color: $tc-black; - font-weight: 500; - font-size: 14px; - line-height: 22px; - - @include xs-to-sm { - padding: 0 10px; - width: 80%; - } - } - - .v5-id { - font-weight: 400; - line-height: 22px; - } - - .status-col { - text-align: center; - - button { - background: none; - border: none; - padding: 0; - - .pending { - text-transform: initial; - font-size: 15px; - color: $tc-gray-40; - line-height: $submission-space-20; - } - } - } - - .action-col { - text-align: center; - min-width: 120px; - - @include xs-to-sm { - position: absolute; - right: 0; - top: 10px; - padding: 10px 0; - min-width: 100px; - text-align: right; - - svg { - width: 14px; - height: 14px; - } - } - - .delete-icon { - margin: 0 0 0 24px; - - @include xs-to-sm { - margin-left: 15px; - } - } - - button { - margin-left: 15px; - margin-top: 3px; - background: none; - border: 0; - font-size: 0; - padding: 0; - line-height: 0; - display: inline-block; - - &:focus { - outline: none; - } - - &:disabled { - opacity: 0.5; - } - } - - .expand-icon { - transition: all 1.5s; - margin-left: 24px; - - @include xs-to-sm { - margin-left: 15px; - } - - &.expanded { - transform: rotate(180deg); - } - } - } - - .status-col button:focus { - outline: none; - } -} diff --git a/src/apps/earn/src/components/SubmissionManagement/SubmissionManagement/index.jsx b/src/apps/earn/src/components/SubmissionManagement/SubmissionManagement/index.jsx deleted file mode 100644 index 897e45451..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/SubmissionManagement/index.jsx +++ /dev/null @@ -1,223 +0,0 @@ -/** - * This component should render the entire page assembly, - * but not yet implement the logic behind user actions. - * It still receives submissions and challenge data, all callbacks, etc. from its parent container - * - * Namely, it receives via props: the mock data object (provided along with this specs), - * the showDetails set, and config object holding all necessary callbacks: - * onBack() - to trigger when user clicks Back button under the challenge name; - * onDelete(submissionId); - * onDownload() (to be triggered by download icon) - * onOpenOnlineReview(submissionId); onHelp(submissionId); - * onShowDetails(submissionId); - * onSubmit() - to trigger when user clicks Add Submission button. - */ - -import _ from 'lodash'; -import PT from 'prop-types'; -import moment from 'moment'; - -import { IconOutline, LinkButton, LoadingCircles } from '~/libs/ui'; -import { phaseEndDate } from '@earn/utils/challenge-listing/helper'; - -import SubmissionsTable from '../SubmissionsTable'; - -import styles from './styles.scss'; - -export default function SubmissionManagement(props) { - const { - challenge, - submissions, - loadingSubmissions, - showDetails, - onDelete, - helpPageUrl, - onDownload, - onShowDetails, - challengeUrl, - onlineReviewUrl, - submissionPhaseStartDate, - } = props; - - const { track } = challenge; - - const challengeType = track.toLowerCase(); - - const isDesign = challengeType === 'design'; - const isDevelop = challengeType === 'development'; - const currentPhase = challenge.phases - .filter(p => p.name !== 'Registration' && p.isOpen) - .sort((a, b) => moment(a.scheduledEndDate).diff(b.scheduledEndDate))[0]; - const submissionPhase = challenge.phases.filter(p => p.name === 'Submission')[0]; - const submissionEndDate = submissionPhase && phaseEndDate(submissionPhase); - - const now = moment(); - const end = moment(currentPhase && currentPhase.scheduledEndDate); - const diff = end.isAfter(now) ? end.diff(now) : 0; - const timeLeft = moment.duration(diff); - - const [days, hours, minutes] = [ - timeLeft.get('days'), - timeLeft.get('hours'), - timeLeft.get('minutes'), - ]; - - const componentConfig = { - helpPageUrl, - onDelete, - onDownload: onDownload.bind(0, challengeType), - onlineReviewUrl, - onShowDetails, - }; - return ( -
-
-
- - -

- {challenge.name} -

-
-
-
-
-

- Manage your submissions -

- - {/* { - isDesign && currentPhase && ( -

- - {currentPhase.name} - {' '} - Ends: - - {' '} - {end.format('dddd MM/DD/YY hh:mm A')} -

- ) - } */} -
-
- { - currentPhase && ( -

- Current Deadline:{' '} - {currentPhase.name} -

- ) - } - - { - challenge.status !== 'Completed' ? ( -
-

- Current Deadline Ends: {' '} - - {days > 0 && (`${days}D`)} - {' '} - {hours} - H - {' '} - {minutes} - M - -

-
- ) : ( -

- The challenge has ended -

- ) - } -
- { - isDesign && ( -

- {/* eslint-disable-next-line max-len */} - We always recommend to download your submission to check you uploaded the correct .zip files  - {/* eslint-disable-next-line max-len */} - and also to verify your declarations file is accurate. If you don’t want to see a submission  - {/* eslint-disable-next-line max-len */} - simply delete it. If you have a new submissions, use the “Add Submission” button to add one  - to the top of the list. -

- ) - } - { - isDevelop && ( -

- {/* eslint-disable-next-line max-len */} - We always recommend to download your submission to check you uploaded the correct .zip files  - {/* eslint-disable-next-line max-len */} - and also to verify your declarations file is accurate. If you don’t want to see a submission  - {/* eslint-disable-next-line max-len */} - simply delete it. If you have a new submissions, use the “Add Submission” button to add one  - to the top of the list. -

- ) - } - {loadingSubmissions && } - {!loadingSubmissions - && ( - - ) - } -
- {now.isBefore(submissionEndDate) && ( -
- - { - (!isDevelop || !submissions || submissions.length === 0) - ? 'Add Submission' : 'Update Submission' - } - -
- )} -
- ); -} - -SubmissionManagement.defaultProps = { - onDelete: _.noop, - onShowDetails: _.noop, - onDownload: _.noop, - onlineReviewUrl: '', - helpPageUrl: '', - loadingSubmissions: false, - challengeUrl: '', - submissions: [], -}; - -SubmissionManagement.propTypes = { - showDetails: PT.shape().isRequired, - onDelete: PT.func, - onlineReviewUrl: PT.string, - helpPageUrl: PT.string, - onDownload: PT.func, - onShowDetails: PT.func, - challenge: PT.shape().isRequired, - submissions: PT.arrayOf(PT.shape()), - loadingSubmissions: PT.bool, - challengeUrl: PT.string, - submissionPhaseStartDate: PT.string.isRequired, -}; diff --git a/src/apps/earn/src/components/SubmissionManagement/SubmissionManagement/styles.scss b/src/apps/earn/src/components/SubmissionManagement/SubmissionManagement/styles.scss deleted file mode 100644 index 8c884499f..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/SubmissionManagement/styles.scss +++ /dev/null @@ -1,286 +0,0 @@ -@import '@earn/styles/mixins'; -$submang-space-140: $base-unit * 28; -$submang-space-50: $base-unit * 10; -$submang-space-35: $base-unit * 7; -$submang-space-30: $base-unit * 6; -$submang-space-25: $base-unit * 5; -$submang-space-20: $base-unit * 4; -$gray-color: $tc-gray-40; -$light-gray-color: $tc-gray-neutral-light; - -.btn-wrap { - display: flex; - justify-content: flex-end; - margin-right: 32px; - margin-top: 34px; - color: white; - - @include xs-to-md { - margin-right: 16px; - margin-top: 32px; - } -} - - -.add-sub-btn-warning { - @include roboto-bold; - - font-weight: 700; - font-size: 16px; - line-height: 24px; - border-radius: 50px !important; - background: $tc-red-70 !important; - color: #fff !important; - text-transform: uppercase; - - @include xs-to-md { - font-size: 14px; - line-height: 20px; - } -} - -.submission-management { - padding-bottom: 40px; -} - -.submission-management-content { - padding: 0 32px; - - @include md { - padding: $submang-space-20 $submang-space-35; - } - - @include xs-to-sm { - padding: 0; - } - - .content-head { - display: flex; - justify-content: space-between; - font-weight: 400; - font-size: 15px; - color: $tc-gray-50; - line-height: $submang-space-25; - margin-top: 36px; - margin-bottom: $base-unit; - - @include xs-to-sm { - flex-direction: column; - margin-top: $submang-space-20; - padding: 0 15px; - } - - .title { - @include barlow-bold; - - font-weight: 600; - font-size: 24px; - color: $tc-black; - line-height: 28px; - text-transform: uppercase; - - @include xs-to-md { - font-size: 20px; - line-height: 22px; - } - } - - .round-ends { - font-size: 15px; - color: $tc-black; - line-height: $submang-space-30; - - @include xs-to-sm { - margin-top: 10px; - margin-bottom: 15px; - } - - span.ends-label { - color: $tc-gray-50; - - @include xs-to-sm { - display: block; - margin-bottom: -10px; - } - } - } - } - - .subTitle { - display: flex; - margin-top: 12px; - - @include xs-to-md { - margin: 17px 16px 0 16px; - flex-direction: column; - } - - .round { - @include roboto-regular; - - font-weight: 400; - font-size: 16px; - line-height: 24px; - color: $tc-black; - - span { - @include roboto-bold; - - font-weight: 700; - } - - @include xs-to-md { - font-size: 14px; - line-height: 20px; - } - } - - .seperator { - width: 2px; - height: 24px; - left: 228px; - top: 0; - background: #d4d4d4; - border-radius: 1px; - margin: 0 16px; - - @include xs-to-md { - display: none; - } - } - } - - .recommend-info { - @include roboto-regular; - - font-size: 16px; - font-weight: 400; - color: $tc-black; - line-height: 24px; - margin-bottom: $submang-space-30; - margin-top: 40px; - - @include xs-to-sm { - padding: 0 15px; - margin-top: 32px; - font-size: 14px; - line-height: 20px; - } - } -} - -.submission-management-header { - display: flex; - justify-content: space-between; - width: 100%; - font-weight: 400; - padding: $submang-space-25 32px 0 32px; - background: #fff; - border-bottom: 1px solid $tc-gray-neutral-dark; - - @include md { - padding: $submang-space-25 $submang-space-35; - } - - @include xs-to-sm { - flex-direction: column; - padding: 9px 16px; - } - - .left-col { - display: flex; - padding-right: 200px; - margin-top: 15px; - margin-bottom: 40px; - - @include xs-to-sm { - padding-right: 0; - display: flex; - flex-direction: column; - } - - .name { - @include barlow-bold; - - font-size: 34px; - color: $tc-black; - line-height: 32px; - font-weight: 600; - margin-left: 16px; - margin-top: -6px; - - @include xs-to-sm { - font-size: 28px; - line-height: 32px; - margin-top: 0; - margin-left: 0; - } - } - - .back-btn { - @include roboto-bold; - - font-weight: 700; - font-size: 16px; - line-height: 24px; - border: 0; - color: #137d60 !important; - width: 32px; - height: 24px; - position: relative; - - @include xs-to-md { - margin-left: 0; - margin-bottom: 8px; - } - - svg { - border: 1.5px solid #137d60; - border-radius: 24px; - - @include xs-to-md { - margin-left: 0; - margin-bottom: 15px; - } - } - } - } - - .right-col { - @include roboto-regular; - - min-width: 100px; - - @include xs-to-sm { - margin-top: 10px; - - p, - div { - display: inline-block; - } - } - - .time-left { - font-size: 20px; - color: $tc-gray-80; - line-height: $submang-space-30; - - @include xs-to-sm { - font-size: 15px; - font-weight: bold; - line-height: $submang-space-25; - margin-left: 5px; - } - } - - .left-label { - font-size: 13px; - color: $tc-gray-50; - line-height: $submang-space-20; - - @include xs-to-sm { - margin-left: 5px; - } - } - } -} diff --git a/src/apps/earn/src/components/SubmissionManagement/SubmissionsTable/index.jsx b/src/apps/earn/src/components/SubmissionManagement/SubmissionsTable/index.jsx deleted file mode 100644 index d55aeb8b9..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/SubmissionsTable/index.jsx +++ /dev/null @@ -1,153 +0,0 @@ -/** - * This component will render the entire assemly of submissions table. - * It receives via props the array of submission data objects; - * a showDetails set with submission IDs for which details panel - * should be shown; and type property (design or develop) to know - * whether the screening status column should be rendered. - * - * Also it will receive a bunch of callbacks which should be properly - * wired to the children components: - * onDelete(submissionId) – to delete specified submission; - * onDownload(submissionId) – to download the specified submission; - * onOpenOnlineReview(submissionId); - * onHelp(submissionId); - * onShowDetails(submissionId). - */ - -import _ from 'lodash'; -import React from 'react'; -import PT from 'prop-types'; -import shortid from 'shortid'; -import moment from 'moment'; -import { COMPETITION_TRACKS } from '@earn/utils/tc'; -import Submission from '../Submission'; -import ScreeningDetails from '../ScreeningDetails'; -import styles from './styles.scss'; - -export default function SubmissionsTable(props) { - const { - submissionObjects, - showDetails, - track, - onDelete, - onlineReviewUrl, - helpPageUrl, - onDownload, - onShowDetails, - status, - submissionPhaseStartDate, - } = props; - - const submissionsWithDetails = []; - if (!submissionObjects || submissionObjects.length === 0) { - submissionsWithDetails.push(( - - - You have no submission uploaded so far. - - - )); - } else { - submissionObjects.forEach((subObject) => { - // submissionPhaseStartDate will be the start date of - // the current submission/checkpoint or empty string if any other phase - const allowDelete = submissionPhaseStartDate - && moment(subObject.submissionDate).isAfter(submissionPhaseStartDate); - - const submission = ( - - ); - submissionsWithDetails.push(submission); - - const submissionDetail = ( - - {showDetails[subObject.id] - && ( - - - - )} - - ); - submissionsWithDetails.push(submissionDetail); - }); - } - - return ( -
- - - - - - - {track === COMPETITION_TRACKS.DES && ( - - )} - - - - - {submissionsWithDetails} - -
- ID - - Type - - Submission Date - - Screening Status - - Actions -
-
- ); -} - -const SubShape = PT.shape({ - id: PT.string, - warpreviewnings: PT.string, - screening: PT.shape({ - status: PT.string, - }), - submitted: PT.string, - track: PT.string, -}); - -SubmissionsTable.defaultProps = { - submissionObjects: [], - onDelete: _.noop, - onDownload: _.noop, - onShowDetails: _.noop, - onlineReviewUrl: '', - helpPageUrl: '', -}; - -SubmissionsTable.propTypes = { - submissionObjects: PT.arrayOf(SubShape), - showDetails: PT.shape().isRequired, - track: PT.string.isRequired, - onDelete: PT.func, - onlineReviewUrl: PT.string, - helpPageUrl: PT.string, - onDownload: PT.func, - onShowDetails: PT.func, - status: PT.string.isRequired, - submissionPhaseStartDate: PT.string.isRequired, -}; diff --git a/src/apps/earn/src/components/SubmissionManagement/SubmissionsTable/styles.scss b/src/apps/earn/src/components/SubmissionManagement/SubmissionsTable/styles.scss deleted file mode 100644 index 498a0fac7..000000000 --- a/src/apps/earn/src/components/SubmissionManagement/SubmissionsTable/styles.scss +++ /dev/null @@ -1,164 +0,0 @@ -@import '@earn/styles/mixins'; -$status-space-10: $base-unit * 2; -$status-space-20: $base-unit * 4; -$status-space-25: $base-unit * 5; -$submission-space-10: $base-unit * 2; -$submission-space-20: $base-unit * 4; -$submission-space-25: $base-unit * 5; -$submission-space-50: $base-unit * 10; - -.submissions-table { - // border: 1px solid $tc-gray-10; - overflow: hidden; - // border-radius: 4px 4px 0 0; - - @include xs-to-sm { - border: none; - border-radius: 0; - border-top: 1px solid $tc-gray-10; - border-bottom: 1px solid $tc-gray-10; - margin: 0 16px; - } - - table { - width: 100%; - - th { - @include barlow-bold; - - font-size: 11px; - color: #767676; - font-weight: 600; - line-height: $status-space-20; - text-align: left; - padding: 0 15px 11px 15px; - text-transform: uppercase; - - &.status, - &.actions { - text-align: center; - } - - @include xs-to-sm { - display: none; - } - } - - .no-submission { - line-height: $submission-space-20; - padding: $submission-space-50 $submission-space-20; - text-align: center; - } - } - - .status-col { - text-align: center; - } - - .action-col { - text-align: center; - } -} - -.submission-row { - width: 100%; - font-size: 15px; - color: $tc-black; - font-weight: 400; - - td { - vertical-align: middle; - padding: 16px 16px 2px 16px; - background: $tc-white; - font-size: 14px; - color: $tc-black; - font-weight: 500; - border-top: 1px solid $tc-gray-10; - line-height: 12px; - - @include xs-to-md { - padding: 16px 16px 2px 8px; - } - - &.no-submission { - line-height: $submission-space-20; - padding: $submission-space-50 $submission-space-20; - text-align: center; - } - - &.dev-details { - padding-right: 60px; - border-top: 0; - padding-top: 0; - } - } - - .id-col { - font-weight: 700; - width: 32%; - } - - .type-col { - width: 21%; - } - - .date-col { - width: 20%; - color: $tc-black; - font-weight: 500; - line-height: 22px; - font-size: 14px; - } - - .status-col { - text-align: center; - - button { - background: none; - border: none; - padding: 0; - - .pending { - text-transform: initial; - font-size: 15px; - color: $tc-gray-40; - line-height: $submission-space-20; - } - } - } - - .action-col { - text-align: center; - - .delete-icon { - margin: 0 $submission-space-25; - } - - button { - background: none; - border: 0; - font-size: 0; - padding: 0; - line-height: 0; - display: inline-block; - margin-left: 15px; - margin-top: 3px; - - &:focus { - outline: none; - } - } - - .expand-icon { - transition: all 1.5s; - - &.expanded { - transform: rotate(180deg); - } - } - } - - .status-col button:focus { - outline: none; - } -} diff --git a/src/apps/earn/src/components/Tag/index.jsx b/src/apps/earn/src/components/Tag/index.jsx deleted file mode 100644 index bdbf35d55..000000000 --- a/src/apps/earn/src/components/Tag/index.jsx +++ /dev/null @@ -1,24 +0,0 @@ -import React from "react"; -import PT from "prop-types"; - -import styles from "./styles.scss"; - -const Tag = ({ tag, onClick }) => ( - { - onClick(tag); - }} - > - {tag} - -); - -Tag.propTypes = { - tag: PT.string, - onClick: PT.func, -}; - -export default Tag; diff --git a/src/apps/earn/src/components/Tag/styles.scss b/src/apps/earn/src/components/Tag/styles.scss deleted file mode 100644 index 81e4dc9f0..000000000 --- a/src/apps/earn/src/components/Tag/styles.scss +++ /dev/null @@ -1,13 +0,0 @@ -@import "@earn/styles/variables"; - -.tag { - display: inline-block; - padding: 3px 4px 3px 5px; - font-size: $font-size-xs; - line-height: 15px; - letter-spacing: 0.55px; - white-space: nowrap; - border: 1px solid $body-color; - border-radius: $border-radius-sm; - cursor: pointer; -} diff --git a/src/apps/earn/src/components/TagList/index.jsx b/src/apps/earn/src/components/TagList/index.jsx deleted file mode 100644 index 4db83b3eb..000000000 --- a/src/apps/earn/src/components/TagList/index.jsx +++ /dev/null @@ -1,148 +0,0 @@ -import { - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from "react"; -import PT from "prop-types"; - -import { Tooltip } from "~/libs/ui"; - -import styles from "./styles.scss"; - -/** - * Displays a tooltip with tags that are not initially shown. - * - * @param {Object} props component properties - * @returns {JSX.Element} - */ -const TagsTooltip = ({ children, onClickTag, renderTag, tags }) => { - const overlay = useMemo( - () => ( -
- {tags - .map((tag) => ({ - tag, - onClickTag, - className: styles.tooltipTag, - })) - .map(renderTag)} -
- ), - [onClickTag, renderTag, tags] - ); - return ( - - {children} - - ); -}; - -/** - * Displays a tag list. Initially displays only the first line of tags. - * - * @param {Object} props component properties - * @param {string} [props.className] class name added to root element - * @param {number} [props.maxTagCount] maximum number of tags to show - * @param {(t: any) => void} [props.onClickTag] function called when tag is clicked - * @param {(t: any) => JSX.Element} props.renderTag function that renders a tag - * @param {Array} props.tags array of tags - * @returns {JSX.Element} - */ -const TagList = ({ - className, - maxTagCount = 5, - onClickTag, - renderTag, - tags, -}) => { - const [isVisible, setIsVisible] = useState(false); - const [showAll, setShowAll] = useState(false); - const [tagsVisible, setTagsVisible] = useState(tags); - - const containerRef = useRef(); - - const onClickBtnMore = useCallback( - (event) => { - event.preventDefault(); - setTagsVisible(tags); - setShowAll(true); - }, - [tags] - ); - - useEffect(() => { - let index = getFirstLineLastElemIndex(containerRef.current); - index = index < 0 ? maxTagCount : Math.min(index, maxTagCount); - if (tags.length < index + 1) { - setShowAll(true); - setTagsVisible(tags); - } else { - setTagsVisible(tags.slice(0, index)); - } - setIsVisible(true); - }, [tags, maxTagCount]); - - return ( -
- {tagsVisible - .map((tag) => ({ className: styles.tag, onClickTag, tag })) - .map(renderTag)} - {!showAll && ( - - - {tags.length - tagsVisible.length}+ - - - )} -
- ); -}; - -TagList.propTypes = { - className: PT.string, - maxTagCount: PT.number, - onClickTag: PT.func.isRequired, - renderTag: PT.func.isRequired, - tags: PT.arrayOf(PT.object).isRequired, -}; - -export default TagList; - -/** - * Searches for the index of the last element on the first line. - * - * @param {Element} container container element - * @returns {number} first line's last element's index - */ -function getFirstLineLastElemIndex(container) { - const firstElement = container.firstElementChild; - let offsetTop = firstElement.offsetTop; - for ( - let elem = firstElement.nextElementSibling, index = 1; - elem; - elem = elem.nextElementSibling, index++ - ) { - if (elem.offsetTop > offsetTop) { - return index - 1; - } - } - return -1; -} diff --git a/src/apps/earn/src/components/TagList/styles.scss b/src/apps/earn/src/components/TagList/styles.scss deleted file mode 100644 index 5fd9114c1..000000000 --- a/src/apps/earn/src/components/TagList/styles.scss +++ /dev/null @@ -1,56 +0,0 @@ -@import "../../styles/mixins"; -@import "../../styles/variables"; - -.container { - position: relative; - display: flex; - flex-wrap: wrap; - height: 27px; - opacity: 0; - - &.visible { - height: auto; - opacity: 1; - } -} - -.btn-more, -.tag { - margin-top: 4px; - margin-right: 4px; - border: 1px solid $body-color; - border-radius: 5px; - padding: 4px 6px 2px; - font-size: 12px; - line-height: 15px; - cursor: pointer; - - &:last-child { - margin-right: 0; - } -} - -.tooltip-tag-list { - display: flex; - flex-wrap: wrap; - background-color: transparent; -} - -.tooltipTag { - flex: 0 0 auto; - margin-top: 4px; - margin-right: 4px; - border-radius: 5px; - padding: 4px 5px 4px 6px; - @include roboto-regular; - font-size: 11px; - letter-spacing: 0.5px; - line-height: 15px; - color: #fff; - background-color: #555; - cursor: pointer; - - &:last-child { - margin-right: 0; - } -} diff --git a/src/apps/earn/src/components/Terms/TermDetails.jsx b/src/apps/earn/src/components/Terms/TermDetails.jsx deleted file mode 100644 index 8aaff868c..000000000 --- a/src/apps/earn/src/components/Terms/TermDetails.jsx +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Terms details component which display text of an agreement - */ - -import React from "react"; -import PT from "prop-types"; - -import { styled as styledCss } from "@earn/utils"; -import { LoadingCircles } from "~/libs/ui"; - -import styles from "./TermDetails.module.scss"; - -const styled = styledCss(styles); - -export default class TermDetails extends React.Component { - constructor(props) { - super(props); - this.state = { - loadingFrame: false, - }; - this.frameLoaded = this.frameLoaded.bind(this); - } - - componentWillMount() { - const { details, getDocuSignUrl } = this.props; - if ( - details.agreeabilityType !== "Electronically-agreeable" && - details.docusignTemplateId - ) { - getDocuSignUrl(details.docusignTemplateId); - this.setState({ loadingFrame: true }); - } - } - - frameLoaded() { - this.setState({ - loadingFrame: false, - }); - } - - render() { - const { details, docuSignUrl, loadingDocuSignUrl } = this.props; - const { loadingFrame } = this.state; - - return ( -
- {details.agreeabilityType === "Electronically-agreeable" && ( -
-
-
- )} - {details.agreeabilityType !== "Electronically-agreeable" && - details.docusignTemplateId === loadingDocuSignUrl && ( - - )} - {details.agreeabilityType !== "Electronically-agreeable" && - details.docusignTemplateId && - !loadingDocuSignUrl && - docuSignUrl && ( -
- {loadingFrame && } -