diff --git a/.github/dependabot.yml b/.github/_dependabot.yml similarity index 100% rename from .github/dependabot.yml rename to .github/_dependabot.yml diff --git a/.github/dependency-review-config.yml b/.github/dependency-review-config.yml new file mode 100644 index 0000000000..6c8f51e130 --- /dev/null +++ b/.github/dependency-review-config.yml @@ -0,0 +1,4 @@ +show-openssf-scorecard: false +fail-on-severity: 'high' +allow-ghsas: + - GHSA-qwww-vcr4-c8h2 # we don't use vulnerable functionality \ No newline at end of file diff --git a/.github/workflows/cd-packages.yaml b/.github/workflows/cd-packages.yaml index 2b659d27cc..3a5b970caf 100644 --- a/.github/workflows/cd-packages.yaml +++ b/.github/workflows/cd-packages.yaml @@ -39,7 +39,7 @@ jobs: run: | git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn diff --git a/.github/workflows/cd-python-sdk.yaml b/.github/workflows/cd-python-sdk.yaml index aba50ea0f8..cfd7aba1bf 100644 --- a/.github/workflows/cd-python-sdk.yaml +++ b/.github/workflows/cd-python-sdk.yaml @@ -30,12 +30,12 @@ jobs: run: | git config --global user.name "github-actions[bot]" git config --global user.email "github-actions[bot]@users.noreply.github.com" - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn - name: Set up Python - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: "3.x" - name: Read package version diff --git a/.github/workflows/cd-subgraph-hmt.yaml b/.github/workflows/cd-subgraph-hmt.yaml index 5b53b69c26..62752bcaa5 100644 --- a/.github/workflows/cd-subgraph-hmt.yaml +++ b/.github/workflows/cd-subgraph-hmt.yaml @@ -30,7 +30,7 @@ jobs: max-parallel: 3 steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn diff --git a/.github/workflows/cd-subgraph-human.yaml b/.github/workflows/cd-subgraph-human.yaml index 4a13ba84ed..17d87f4a74 100644 --- a/.github/workflows/cd-subgraph-human.yaml +++ b/.github/workflows/cd-subgraph-human.yaml @@ -30,7 +30,7 @@ jobs: max-parallel: 3 steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn diff --git a/.github/workflows/ci-dependency-review.yaml b/.github/workflows/ci-dependency-review.yaml index 4ce01c8511..6583ab0c67 100644 --- a/.github/workflows/ci-dependency-review.yaml +++ b/.github/workflows/ci-dependency-review.yaml @@ -16,4 +16,5 @@ jobs: - name: Dependency Review uses: actions/dependency-review-action@v5.0.0 with: - show-openssf-scorecard: false + config-file: './.github/dependency-review-config.yml' + diff --git a/.github/workflows/ci-lint-cvat-exchange-oracle.yaml b/.github/workflows/ci-lint-cvat-exchange-oracle.yaml index 7b4d925fbb..40cb434fe0 100644 --- a/.github/workflows/ci-lint-cvat-exchange-oracle.yaml +++ b/.github/workflows/ci-lint-cvat-exchange-oracle.yaml @@ -24,7 +24,7 @@ jobs: uses: snok/install-poetry@v1 with: version: 1.8.4 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: '3.10' cache: 'poetry' diff --git a/.github/workflows/ci-lint-cvat-recording-oracle.yaml b/.github/workflows/ci-lint-cvat-recording-oracle.yaml index 046a857d0b..8bab8e5103 100644 --- a/.github/workflows/ci-lint-cvat-recording-oracle.yaml +++ b/.github/workflows/ci-lint-cvat-recording-oracle.yaml @@ -24,10 +24,10 @@ jobs: uses: snok/install-poetry@v1 with: version: 1.8.4 - - uses: actions/setup-python@v6 + - uses: actions/setup-python@v7 with: python-version: '3.10' cache: 'poetry' cache-dependency-path: ${{ env.WORKING_DIR }}/poetry.lock - - run: poetry install --no-root --only lint + - run: poetry install --no-root --only dev - run: poetry run pre-commit run --all-files diff --git a/.github/workflows/ci-lint.yaml b/.github/workflows/ci-lint.yaml index efc5eba32b..07950c2785 100644 --- a/.github/workflows/ci-lint.yaml +++ b/.github/workflows/ci-lint.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn diff --git a/.github/workflows/ci-test-core.yaml b/.github/workflows/ci-test-core.yaml index 6f5e30e46c..adab294c27 100644 --- a/.github/workflows/ci-test-core.yaml +++ b/.github/workflows/ci-test-core.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn diff --git a/.github/workflows/ci-test-cvat-exchange-oracle.yaml b/.github/workflows/ci-test-cvat-exchange-oracle.yaml index 8ec4a147ba..8f49f2b7f4 100644 --- a/.github/workflows/ci-test-cvat-exchange-oracle.yaml +++ b/.github/workflows/ci-test-cvat-exchange-oracle.yaml @@ -15,4 +15,4 @@ jobs: - uses: actions/checkout@v7 - name: CVAT Exchange Oracle tests working-directory: ./packages/examples/cvat/exchange-oracle - run: docker compose -f docker-compose.test.yml up --attach test --exit-code-from test + run: docker compose -f docker-compose.test.yml -f docker-compose.test.head.yml up --attach test --exit-code-from test diff --git a/.github/workflows/ci-test-cvat-recording-oracle.yaml b/.github/workflows/ci-test-cvat-recording-oracle.yaml index bf975f3ce1..944e079de2 100644 --- a/.github/workflows/ci-test-cvat-recording-oracle.yaml +++ b/.github/workflows/ci-test-cvat-recording-oracle.yaml @@ -15,4 +15,4 @@ jobs: - uses: actions/checkout@v7 - name: CVAT Recording Oracle tests working-directory: ./packages/examples/cvat/recording-oracle - run: docker compose -f docker-compose.test.yml up --attach test --exit-code-from test + run: docker compose -f docker-compose.test.yml -f docker-compose.test.head.yml up --attach test --exit-code-from test diff --git a/.github/workflows/ci-test-dashboard.yaml b/.github/workflows/ci-test-dashboard.yaml index 8465fa703e..52fb6163b6 100644 --- a/.github/workflows/ci-test-dashboard.yaml +++ b/.github/workflows/ci-test-dashboard.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn diff --git a/.github/workflows/ci-test-faucet-server.yaml b/.github/workflows/ci-test-faucet-server.yaml index c9df4aa014..87fa2a0306 100644 --- a/.github/workflows/ci-test-faucet-server.yaml +++ b/.github/workflows/ci-test-faucet-server.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn diff --git a/.github/workflows/ci-test-fortune.yaml b/.github/workflows/ci-test-fortune.yaml index 7e41bb531a..e51171b77b 100644 --- a/.github/workflows/ci-test-fortune.yaml +++ b/.github/workflows/ci-test-fortune.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn @@ -30,7 +30,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn diff --git a/.github/workflows/ci-test-human-app.yaml b/.github/workflows/ci-test-human-app.yaml index 422db8ad55..2174d83bd4 100644 --- a/.github/workflows/ci-test-human-app.yaml +++ b/.github/workflows/ci-test-human-app.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn diff --git a/.github/workflows/ci-test-job-launcher.yaml b/.github/workflows/ci-test-job-launcher.yaml index d28bb8d2c6..defaec586e 100644 --- a/.github/workflows/ci-test-job-launcher.yaml +++ b/.github/workflows/ci-test-job-launcher.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn diff --git a/.github/workflows/ci-test-node-sdk.yaml b/.github/workflows/ci-test-node-sdk.yaml index 0420a09432..e065c841f0 100644 --- a/.github/workflows/ci-test-node-sdk.yaml +++ b/.github/workflows/ci-test-node-sdk.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn diff --git a/.github/workflows/ci-test-python-sdk.yaml b/.github/workflows/ci-test-python-sdk.yaml index a201efebcc..09f60e0872 100644 --- a/.github/workflows/ci-test-python-sdk.yaml +++ b/.github/workflows/ci-test-python-sdk.yaml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn @@ -22,7 +22,7 @@ jobs: - name: Build core package run: yarn workspace @human-protocol/core build - name: Set up Python 3.10 - uses: actions/setup-python@v6 + uses: actions/setup-python@v7 with: python-version: "3.10" - name: Install pipenv diff --git a/.github/workflows/ci-test-reputation-oracle.yaml b/.github/workflows/ci-test-reputation-oracle.yaml index c5248a8e1e..e8497e7012 100644 --- a/.github/workflows/ci-test-reputation-oracle.yaml +++ b/.github/workflows/ci-test-reputation-oracle.yaml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn diff --git a/.github/workflows/ci-test-subgraph-hmt.yaml b/.github/workflows/ci-test-subgraph-hmt.yaml index e7913ed99c..28b9767236 100644 --- a/.github/workflows/ci-test-subgraph-hmt.yaml +++ b/.github/workflows/ci-test-subgraph-hmt.yaml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn diff --git a/.github/workflows/ci-test-subgraph-human.yaml b/.github/workflows/ci-test-subgraph-human.yaml index f7cb543cb2..3a06bad898 100644 --- a/.github/workflows/ci-test-subgraph-human.yaml +++ b/.github/workflows/ci-test-subgraph-human.yaml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v7 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@v7 with: node-version-file: .nvmrc cache: yarn diff --git a/docker-setup/Makefile b/docker-setup/Makefile index 724723d377..39d57fd2e6 100644 --- a/docker-setup/Makefile +++ b/docker-setup/Makefile @@ -16,15 +16,15 @@ infra-up: check-env-file infra-stop: @docker compose --env-file .env.compose.local stop postgres redis minio minio-client +# TODO: Revisit compose-native parallel builds once Docker Compose v5 parallel limits are reliable again. build-services: check-env-file - @# TODO: Revisit compose-native parallel builds once Docker Compose v5 parallel limits are reliable again. @docker compose --env-file .env.compose.local config --services | \ xargs -n $(DOCKER_PARALLEL) docker compose --env-file .env.compose.local build @docker compose --env-file .env.compose.local up --no-start +# TODO: Revisit compose-native parallel builds once Docker Compose v5 parallel limits are reliable again. services-up: check-env-file @service_names="$(wordlist 2, $(words $(MAKECMDGOALS)), $(MAKECMDGOALS))"; \ - @# TODO: Revisit compose-native parallel builds once Docker Compose v5 parallel limits are reliable again. if [ -n "$$service_names" ]; then \ printf '%s\n' $$service_names | xargs -n $(DOCKER_PARALLEL) docker compose --env-file .env.compose.local up -d; \ else \ diff --git a/packages/apps/dashboard/client/package.json b/packages/apps/dashboard/client/package.json index 884bd43dbf..54c43c6c7c 100644 --- a/packages/apps/dashboard/client/package.json +++ b/packages/apps/dashboard/client/package.json @@ -25,7 +25,7 @@ "@mui/styled-engine-sc": "9.0.1", "@mui/system": "^9.0.1", "@mui/x-data-grid": "^8.7.0", - "@mui/x-date-pickers": "^9.2.0", + "@mui/x-date-pickers": "^9.9.0", "@tanstack/react-query": "^5.91.3", "@types/react-router-dom": "^5.3.3", "@types/recharts": "^1.8.29", @@ -35,10 +35,10 @@ "react": "^19.2.6", "react-dom": "^19.2.6", "react-number-format": "^5.4.5", - "react-router-dom": "^7.18.0", + "react-router-dom": "^7.18.1", "recharts": "^2.13.0-alpha.4", "simplebar-react": "^3.3.2", - "styled-components": "^6.3.11", + "styled-components": "^6.4.3", "swiper": "^11.1.3", "use-debounce": "^10.1.0", "vite-plugin-node-polyfills": "^0.25.0", diff --git a/packages/apps/dashboard/server/package.json b/packages/apps/dashboard/server/package.json index d6f2b10fac..d18d0ed3a1 100644 --- a/packages/apps/dashboard/server/package.json +++ b/packages/apps/dashboard/server/package.json @@ -27,7 +27,7 @@ "@keyv/redis": "^5.1.5", "@nestjs/axios": "^4.0.1", "@nestjs/cache-manager": "^3.1.0", - "@nestjs/common": "^11.1.12", + "@nestjs/common": "^11.1.27", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.12", "@nestjs/mapped-types": "^2.1.1", @@ -35,14 +35,14 @@ "@nestjs/schedule": "^6.1.3", "@nestjs/swagger": "^11.2.5", "axios": "^1.3.1", - "cache-manager": "7.2.8", + "cache-manager": "7.2.9", "class-transformer": "^0.5.1", "class-validator": "0.14.1", "dayjs": "^1.11.20", "ethers": "~6.17.0", "joi": "^17.13.3", "keyv": "^5.5.5", - "lodash": "^4.17.21", + "lodash": "^4.18.1", "minio": "8.0.7", "reflect-metadata": "^0.2.2", "rxjs": "^7.2.0" diff --git a/packages/apps/dashboard/server/src/common/config/cache-factory.config.ts b/packages/apps/dashboard/server/src/common/config/cache-factory.config.ts index f7c6350826..049a763aed 100644 --- a/packages/apps/dashboard/server/src/common/config/cache-factory.config.ts +++ b/packages/apps/dashboard/server/src/common/config/cache-factory.config.ts @@ -1,4 +1,4 @@ -import { CacheModuleAsyncOptions } from '@nestjs/common/cache'; +import { CacheModuleAsyncOptions } from '@nestjs/cache-manager'; import { ConfigModule } from '@nestjs/config'; import * as _ from 'lodash'; diff --git a/packages/apps/faucet/client/package.json b/packages/apps/faucet/client/package.json index b24b6c5666..620c0f82a3 100644 --- a/packages/apps/faucet/client/package.json +++ b/packages/apps/faucet/client/package.json @@ -26,7 +26,7 @@ "react": "^19.2.6", "react-dom": "^19.2.6", "react-loading-skeleton": "^3.3.1", - "react-router-dom": "^7.18.0", + "react-router-dom": "^7.18.1", "serve": "^14.2.6", "viem": "^2.52.2" }, diff --git a/packages/apps/fortune/exchange-oracle/client/package.json b/packages/apps/fortune/exchange-oracle/client/package.json index 7d2542af36..80351990c1 100644 --- a/packages/apps/fortune/exchange-oracle/client/package.json +++ b/packages/apps/fortune/exchange-oracle/client/package.json @@ -32,14 +32,14 @@ "@human-protocol/sdk": "workspace:^", "@mui/icons-material": "^7.3.8", "@mui/material": "^5.16.7", - "@tanstack/query-sync-storage-persister": "^5.68.0", + "@tanstack/query-sync-storage-persister": "^5.101.2", "@tanstack/react-query": "^5.91.3", "@tanstack/react-query-persist-client": "^5.80.7", "axios": "^1.7.2", "ethers": "^6.17.0", "react": "^19.2.6", "react-dom": "^19.2.6", - "react-router-dom": "^7.18.0", + "react-router-dom": "^7.18.1", "serve": "^14.2.6", "viem": "^2.52.2", "wagmi": "^3.6.15" diff --git a/packages/apps/fortune/exchange-oracle/server/package.json b/packages/apps/fortune/exchange-oracle/server/package.json index cc725d61fa..3ee2d456da 100644 --- a/packages/apps/fortune/exchange-oracle/server/package.json +++ b/packages/apps/fortune/exchange-oracle/server/package.json @@ -33,7 +33,7 @@ "@human-protocol/logger": "workspace:*", "@human-protocol/sdk": "workspace:*", "@nestjs/axios": "^4.0.1", - "@nestjs/common": "^11.1.12", + "@nestjs/common": "^11.1.27", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.12", "@nestjs/passport": "^11.0.5", diff --git a/packages/apps/fortune/recording-oracle/package.json b/packages/apps/fortune/recording-oracle/package.json index aa930343df..8e4aa52d58 100644 --- a/packages/apps/fortune/recording-oracle/package.json +++ b/packages/apps/fortune/recording-oracle/package.json @@ -26,7 +26,7 @@ "@human-protocol/logger": "workspace:*", "@human-protocol/sdk": "workspace:*", "@nestjs/axios": "^4.0.1", - "@nestjs/common": "^11.1.12", + "@nestjs/common": "^11.1.27", "@nestjs/config": "^4.0.2", "@nestjs/core": "^11.1.12", "@nestjs/platform-express": "^11.1.12", diff --git a/packages/apps/human-app/frontend/.env.example b/packages/apps/human-app/frontend/.env.example index 8a13a7385e..a605cc5028 100644 --- a/packages/apps/human-app/frontend/.env.example +++ b/packages/apps/human-app/frontend/.env.example @@ -17,10 +17,6 @@ VITE_HMT_DAILY_SPENT_LIMIT=0 # hCaptcha annotation tool VITE_H_CAPTCHA_EXCHANGE_URL=replace_me VITE_H_CAPTCHA_LABELING_BASE_URL=replace_me -VITE_H_CAPTCHA_ORACLE_ADDRESS=replace_me -VITE_H_CAPTCHA_ORACLE_ANNOTATION_TOOL=hcaptcha -VITE_H_CAPTCHA_ORACLE_ROLE=hcaptcha -VITE_H_CAPTCHA_ORACLE_TASK_TYPES=image_points,image_boxes # Other VITE_HUMAN_PROTOCOL_HELP_URL=https://docs.humanprotocol.org/ diff --git a/packages/apps/human-app/frontend/package.json b/packages/apps/human-app/frontend/package.json index 79b2befa5c..0d76ca8e37 100644 --- a/packages/apps/human-app/frontend/package.json +++ b/packages/apps/human-app/frontend/package.json @@ -25,32 +25,27 @@ "@hcaptcha/react-hcaptcha": "^1.14.0", "@hookform/resolvers": "^5.1.0", "@human-protocol/sdk": "workspace:*", - "@mui/icons-material": "^7.3.8", - "@mui/material": "^5.16.7", - "@mui/system": "^9.0.1", - "@mui/x-date-pickers": "^9.2.0", + "@mui/icons-material": "^9.2.0", + "@mui/material": "^9.2.0", + "@mui/x-date-pickers": "^9.9.0", "@reown/appkit": "^1.8.20", "@reown/appkit-adapter-wagmi": "^1.8.20", - "@synaps-io/verify-sdk": "^4.0.45", "@tanstack/react-query": "^5.91.3", "@wagmi/core": "^3.4.0", "date-fns": "^4.1.0", "ethers": "^6.17.0", "i18next": "^25.8.0", "jwt-decode": "^4.0.0", - "lodash": "^4.17.21", + "lodash": "^4.18.1", "material-react-table": "3.0.1", - "mui-image": "^1.0.7", "notistack": "^3.0.1", - "prop-types": "^15.8.1", "query-string": "^9.0.0", "react": "^19.2.6", "react-dom": "^19.2.6", "react-hook-form": "^7.71.1", "react-i18next": "^15.1.0", - "react-imask": "^7.4.0", "react-number-format": "^5.4.5", - "react-router-dom": "^7.18.0", + "react-router-dom": "^7.18.1", "serve": "^14.2.6", "viem": "^2.52.2", "vite-plugin-svgr": "^4.2.0", @@ -60,27 +55,20 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", - "@tanstack/eslint-plugin-query": "^5.60.1", - "@tanstack/react-query-devtools": "^5.59.16", - "@types/lodash": "^4.17.12", - "@types/mui-image": "^1.0.5", + "@types/lodash": "^4.17.24", "@types/node": "^22.10.5", - "@types/prop-types": "^15", "@types/react": "^19.2.15", "@types/react-dom": "^19.2.3", - "@vercel/style-guide": "^6.0.0", "@vitejs/plugin-react": "^4.2.1", "eslint": "^10.1.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-prettier": "^5.5.6", "eslint-plugin-react-hooks": "^5.2.0", - "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.7.0", - "husky": "^9.1.6", "jsdom": "^29.1.1", "prettier": "^3.8.1", - "typescript": "^5.6.3", + "typescript": "^6.0.2", "typescript-eslint": "^8.57.0", "vite": "^6.2.4", "vitest": "^4.0.18" diff --git a/packages/apps/human-app/frontend/src/api/auth-service.ts b/packages/apps/human-app/frontend/src/api/auth-service.ts index 177a98c600..f54a1a49c5 100644 --- a/packages/apps/human-app/frontend/src/api/auth-service.ts +++ b/packages/apps/human-app/frontend/src/api/auth-service.ts @@ -7,7 +7,6 @@ import { } from '@/shared/schemas'; import { type BrowserAuthProvider } from '@/shared/types/browser-auth-provider'; import { type HttpApiClient } from './http-api-client'; -import { commonApiPaths } from './common-api-paths'; export interface AuthProvider { getAccessToken: () => Promise; @@ -15,12 +14,9 @@ export interface AuthProvider { } const apiPaths = { - worker: { - signIn: { - path: '/auth/signin', - }, - }, -} as const; + signIn: '/auth/signin', + refresh: '/auth/refresh', +}; export class AuthService implements AuthProvider { private readonly browserAuthProvider: BrowserAuthProvider = @@ -31,16 +27,16 @@ export class AuthService implements AuthProvider { constructor(private readonly httpClient: HttpApiClient) {} - async signIn(data: SignInDto): Promise { + async signIn(data: SignInDto): Promise { const res = await this.httpClient.post( - apiPaths.worker.signIn.path, + apiPaths.signIn, { successSchema: authTokensSuccessResponseSchema, body: data, } ); - this.browserAuthProvider.signIn(res, 'web2'); + return res; } async getAccessToken(): Promise { @@ -70,12 +66,6 @@ export class AuthService implements AuthProvider { } async refreshAccessToken(): Promise { - const authType = this.browserAuthProvider.getAuthType(); - - if (!authType) { - throw new Error('Auth type not found'); - } - if (!AuthService.refreshPromise) { AuthService.refreshPromise = this.fetchTokenRefresh(); } @@ -88,7 +78,7 @@ export class AuthService implements AuthProvider { throw new Error('Failed to refresh access token'); } - browserAuthProvider.signIn(tokens, authType); + browserAuthProvider.signIn(tokens); } private async fetchTokenRefresh(): Promise { @@ -100,7 +90,7 @@ export class AuthService implements AuthProvider { try { const response = await this.httpClient.post( - commonApiPaths.auth.refresh.path, + apiPaths.refresh, { body: { refresh_token: refreshToken, diff --git a/packages/apps/human-app/frontend/src/api/common-api-paths.ts b/packages/apps/human-app/frontend/src/api/common-api-paths.ts deleted file mode 100644 index ed28ab1e4b..0000000000 --- a/packages/apps/human-app/frontend/src/api/common-api-paths.ts +++ /dev/null @@ -1,7 +0,0 @@ -export const commonApiPaths = { - auth: { - refresh: { - path: '/auth/refresh', - }, - }, -} as const; diff --git a/packages/apps/human-app/frontend/src/api/hooks/use-access-token-refresh.ts b/packages/apps/human-app/frontend/src/api/hooks/use-access-token-refresh.ts index 2f74d18fd5..3a7b272677 100644 --- a/packages/apps/human-app/frontend/src/api/hooks/use-access-token-refresh.ts +++ b/packages/apps/human-app/frontend/src/api/hooks/use-access-token-refresh.ts @@ -2,34 +2,28 @@ import { useMutation } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '@/modules/auth/hooks/use-auth'; import { browserAuthProvider } from '@/shared/contexts/browser-auth-provider'; -import type { AuthType } from '@/shared/types/browser-auth-provider'; -import { useWeb3Auth } from '@/modules/auth-web3/hooks/use-web3-auth'; import { routerPaths } from '@/router/router-paths'; import { authService } from '../authorized-http-api-client'; +type RefreshAccessTokenParams = { + throwExpirationModalOnSignOut?: boolean; +}; + export function useAccessTokenRefresh() { const navigate = useNavigate(); - const { signOut: web2SignOut, user: web2User } = useAuth(); - - const { signOut: web3SignOut, user: web3User } = useWeb3Auth(); + const { user, signOut } = useAuth(); const mutation = useMutation({ - mutationFn: async ({ - authType, - throwExpirationModalOnSignOut = true, - }: { - authType: AuthType; - throwExpirationModalOnSignOut?: boolean; - }) => { + mutationFn: async (params?: RefreshAccessTokenParams) => { + const throwExpirationModalOnSignOut = + params?.throwExpirationModalOnSignOut ?? true; + try { await authService.refreshAccessToken(); } catch (error) { console.error(error); - if (authType === 'web2' && web2User) { - web2SignOut({ throwExpirationModal: false }); - } - if (authType === 'web3' && web3User) { - web3SignOut({ throwExpirationModal: false }); + if (user) { + signOut({ throwExpirationModal: false }); } browserAuthProvider.signOut({ triggerSignOutSubscriptions: throwExpirationModalOnSignOut, @@ -45,7 +39,16 @@ export function useAccessTokenRefresh() { }); return { - refreshAccessToken: mutation.mutate, - refreshAccessTokenAsync: mutation.mutateAsync, + refreshAccessToken: ( + params?: RefreshAccessTokenParams, + options?: Parameters[1] + ) => { + mutation.mutate(params, options); + }, + refreshAccessTokenAsync: ( + params?: RefreshAccessTokenParams, + options?: Parameters[1] + ) => mutation.mutateAsync(params, options), + isRefreshingAccessToken: mutation.isPending, }; } diff --git a/packages/apps/human-app/frontend/src/assets/background-images/signin-background-mobile.png b/packages/apps/human-app/frontend/src/assets/background-images/signin-background-mobile.png new file mode 100644 index 0000000000..6fff015e74 Binary files /dev/null and b/packages/apps/human-app/frontend/src/assets/background-images/signin-background-mobile.png differ diff --git a/packages/apps/human-app/frontend/src/assets/background-images/signin-background.png b/packages/apps/human-app/frontend/src/assets/background-images/signin-background.png new file mode 100644 index 0000000000..cb6c066c97 Binary files /dev/null and b/packages/apps/human-app/frontend/src/assets/background-images/signin-background.png differ diff --git a/packages/apps/human-app/frontend/src/assets/background-images/signup-background-mobile.png b/packages/apps/human-app/frontend/src/assets/background-images/signup-background-mobile.png new file mode 100644 index 0000000000..3400ddfe29 Binary files /dev/null and b/packages/apps/human-app/frontend/src/assets/background-images/signup-background-mobile.png differ diff --git a/packages/apps/human-app/frontend/src/assets/background-images/signup-background.png b/packages/apps/human-app/frontend/src/assets/background-images/signup-background.png new file mode 100644 index 0000000000..1fbdb516f6 Binary files /dev/null and b/packages/apps/human-app/frontend/src/assets/background-images/signup-background.png differ diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/content-copy.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/content-copy.svg deleted file mode 100644 index c4c4f89b16..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/content-copy.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/delete-icon.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/delete-icon.svg deleted file mode 100644 index 7be6824ded..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/delete-icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/edit-icon.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/edit-icon.svg deleted file mode 100644 index 37d6a7af11..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/edit-icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/filters-button-icon.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/filters-button-icon.svg deleted file mode 100644 index 4f9807c4df..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/filters-button-icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/filters-icon.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/filters-icon.svg deleted file mode 100644 index 9133868d96..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/filters-icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/hand.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/hand.svg deleted file mode 100644 index 03ed5b0ae5..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/hand.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/hcaptcha-disabled-icon.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/hcaptcha-disabled-icon.svg new file mode 100644 index 0000000000..c4d02582e3 --- /dev/null +++ b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/hcaptcha-disabled-icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/hourglass.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/hourglass.svg new file mode 100644 index 0000000000..2095217f1c --- /dev/null +++ b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/hourglass.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-homepage/logo.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-homepage/logo.svg deleted file mode 100644 index ccc4a10943..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-homepage/logo.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-homepage/mobile-home-icons.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-homepage/mobile-home-icons.svg deleted file mode 100644 index 2f5037a36a..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-homepage/mobile-home-icons.svg +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-homepage/user.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-homepage/user.svg deleted file mode 100644 index b6821ac1a8..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-homepage/user.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-homepage/work.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-homepage/work.svg deleted file mode 100644 index 4417a07e48..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-homepage/work.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-human-logo/human-logo.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-human-logo/human-logo.svg deleted file mode 100644 index 1d1d95f873..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-human-logo/human-logo.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-navbar/human-logo-navbar.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-navbar/human-logo-navbar.svg index c5a20523ef..a10f3b0311 100644 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-navbar/human-logo-navbar.svg +++ b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/icons-navbar/human-logo-navbar.svg @@ -1,16 +1,21 @@ - - - - - - - - + + + + + + + + + + - - + + - - - + + + + + + diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/locker-icon.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/locker-icon.svg deleted file mode 100644 index 062f29fd7a..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/locker-icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/moon.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/moon.svg index 83c14b28d0..df30df2f33 100644 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/moon.svg +++ b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/moon.svg @@ -1,3 +1,3 @@ - - + + diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/profile-icon.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/profile-icon.svg deleted file mode 100644 index f6ed123061..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/profile-icon.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/sort-arrow.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/sort-arrow.svg deleted file mode 100644 index bdde8e1d28..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/sort-arrow.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/sun.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/sun.svg index 676faefe43..4145780958 100644 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/sun.svg +++ b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/sun.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/user-outlined.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/user-outlined.svg deleted file mode 100644 index c359fd0e66..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/user-outlined.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/veriff.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/veriff.svg new file mode 100644 index 0000000000..38f0fac332 --- /dev/null +++ b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/veriff.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/work-header.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/work-header.svg deleted file mode 100644 index e8febf3837..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/work-header.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/work.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/work.svg deleted file mode 100644 index 39ea46187d..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/work.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/chat.svg b/packages/apps/human-app/frontend/src/assets/icons/chat.svg deleted file mode 100644 index f75eab9760..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/chat.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/checkmark-icon.svg b/packages/apps/human-app/frontend/src/assets/icons/checkmark-icon.svg index 645514e815..23af691b61 100644 --- a/packages/apps/human-app/frontend/src/assets/icons/checkmark-icon.svg +++ b/packages/apps/human-app/frontend/src/assets/icons/checkmark-icon.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/packages/apps/human-app/frontend/src/assets/icons/content-copy.svg b/packages/apps/human-app/frontend/src/assets/icons/content-copy.svg deleted file mode 100644 index 4423c76961..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/content-copy.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/delete-icon.svg b/packages/apps/human-app/frontend/src/assets/icons/delete-icon.svg deleted file mode 100644 index 77a98a0a93..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/delete-icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/edit-icon.svg b/packages/apps/human-app/frontend/src/assets/icons/edit-icon.svg deleted file mode 100644 index 3bbfdd7749..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/edit-icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/ethereum-icon.svg b/packages/apps/human-app/frontend/src/assets/icons/ethereum-icon.svg new file mode 100644 index 0000000000..a72b032893 --- /dev/null +++ b/packages/apps/human-app/frontend/src/assets/icons/ethereum-icon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/packages/apps/human-app/frontend/src/assets/icons/filters-button-icon.svg b/packages/apps/human-app/frontend/src/assets/icons/filters-button-icon.svg deleted file mode 100644 index 232d851193..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/filters-button-icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/filters-icon.svg b/packages/apps/human-app/frontend/src/assets/icons/filters-icon.svg deleted file mode 100644 index 724837a70a..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/filters-icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/hand.svg b/packages/apps/human-app/frontend/src/assets/icons/hand.svg deleted file mode 100644 index 81169bd335..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/hand.svg +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/hcaptcha-disabled-icon.svg b/packages/apps/human-app/frontend/src/assets/icons/hcaptcha-disabled-icon.svg new file mode 100644 index 0000000000..4307dd8758 --- /dev/null +++ b/packages/apps/human-app/frontend/src/assets/icons/hcaptcha-disabled-icon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/packages/apps/human-app/frontend/src/assets/icons/hcaptcha.svg b/packages/apps/human-app/frontend/src/assets/icons/hcaptcha.svg new file mode 100644 index 0000000000..65989c188c --- /dev/null +++ b/packages/apps/human-app/frontend/src/assets/icons/hcaptcha.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/apps/human-app/frontend/src/assets/icons/hourglass.svg b/packages/apps/human-app/frontend/src/assets/icons/hourglass.svg new file mode 100644 index 0000000000..ef4ba7468a --- /dev/null +++ b/packages/apps/human-app/frontend/src/assets/icons/hourglass.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/discord.svg b/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/discord.svg deleted file mode 100644 index ee9689095f..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/discord.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/logo.svg b/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/logo.svg deleted file mode 100644 index b9e1bec29c..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/logo.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/mobile-header.svg b/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/mobile-header.svg deleted file mode 100644 index 9e2dd04b78..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/mobile-header.svg +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/mobile-home-icons.svg b/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/mobile-home-icons.svg deleted file mode 100644 index 560edc356f..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/mobile-home-icons.svg +++ /dev/null @@ -1,114 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/user.svg b/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/user.svg deleted file mode 100644 index dc361e58fe..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/user.svg +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/work.svg b/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/work.svg deleted file mode 100644 index c8008e3c77..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/icons-homepage/work.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/icons-human-logo/human-logo.svg b/packages/apps/human-app/frontend/src/assets/icons/icons-human-logo/human-logo.svg deleted file mode 100644 index f2739b8093..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/icons-human-logo/human-logo.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/icons-navbar/human-logo-navbar.svg b/packages/apps/human-app/frontend/src/assets/icons/icons-navbar/human-logo-navbar.svg index 473b53e255..ce92d62218 100644 --- a/packages/apps/human-app/frontend/src/assets/icons/icons-navbar/human-logo-navbar.svg +++ b/packages/apps/human-app/frontend/src/assets/icons/icons-navbar/human-logo-navbar.svg @@ -1,23 +1,21 @@ - - - - - - - - - - - - + + + - - - + + + + + + + + + + - - + + diff --git a/packages/apps/human-app/frontend/src/assets/icons/locker-icon.svg b/packages/apps/human-app/frontend/src/assets/icons/locker-icon.svg deleted file mode 100644 index 80f66da5b8..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/locker-icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/logout.svg b/packages/apps/human-app/frontend/src/assets/icons/logout.svg new file mode 100644 index 0000000000..238986cb0f --- /dev/null +++ b/packages/apps/human-app/frontend/src/assets/icons/logout.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/apps/human-app/frontend/src/assets/icons/moon.svg b/packages/apps/human-app/frontend/src/assets/icons/moon.svg index fb53b8f96b..a0d8dd7557 100644 --- a/packages/apps/human-app/frontend/src/assets/icons/moon.svg +++ b/packages/apps/human-app/frontend/src/assets/icons/moon.svg @@ -1,3 +1,3 @@ - - + + diff --git a/packages/apps/human-app/frontend/src/assets/icons/polygon-icon.svg b/packages/apps/human-app/frontend/src/assets/icons/polygon-icon.svg new file mode 100644 index 0000000000..34d6f6b628 --- /dev/null +++ b/packages/apps/human-app/frontend/src/assets/icons/polygon-icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/packages/apps/human-app/frontend/src/assets/icons/profile-icon.svg b/packages/apps/human-app/frontend/src/assets/icons/profile-icon.svg deleted file mode 100644 index 811121e23f..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/profile-icon.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/packages/apps/human-app/frontend/src/assets/icons/sort-arrow.svg b/packages/apps/human-app/frontend/src/assets/icons/sort-arrow.svg deleted file mode 100644 index 9364f82a04..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/sort-arrow.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/sun.svg b/packages/apps/human-app/frontend/src/assets/icons/sun.svg index 6ff0c48480..4145780958 100644 --- a/packages/apps/human-app/frontend/src/assets/icons/sun.svg +++ b/packages/apps/human-app/frontend/src/assets/icons/sun.svg @@ -1,3 +1,4 @@ - - + + + diff --git a/packages/apps/human-app/frontend/src/assets/icons/triangle.svg b/packages/apps/human-app/frontend/src/assets/icons/triangle.svg new file mode 100644 index 0000000000..917cd60674 --- /dev/null +++ b/packages/apps/human-app/frontend/src/assets/icons/triangle.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/apps/human-app/frontend/src/assets/icons/user-filled.svg b/packages/apps/human-app/frontend/src/assets/icons/user-filled.svg deleted file mode 100644 index ea37160a77..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/user-filled.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/user-outlined.svg b/packages/apps/human-app/frontend/src/assets/icons/user-outlined.svg deleted file mode 100644 index 2a59dbb72c..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/user-outlined.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/user-secondary.svg b/packages/apps/human-app/frontend/src/assets/icons/user-secondary.svg deleted file mode 100644 index 303b047f37..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/user-secondary.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/veriff.svg b/packages/apps/human-app/frontend/src/assets/icons/veriff.svg new file mode 100644 index 0000000000..835e6d7829 --- /dev/null +++ b/packages/apps/human-app/frontend/src/assets/icons/veriff.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/packages/apps/human-app/frontend/src/assets/icons/work-header.svg b/packages/apps/human-app/frontend/src/assets/icons/work-header.svg deleted file mode 100644 index 63e2e5cb89..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/work-header.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/work-secondary.svg b/packages/apps/human-app/frontend/src/assets/icons/work-secondary.svg deleted file mode 100644 index 065a03d862..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/work-secondary.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/packages/apps/human-app/frontend/src/assets/icons/work.svg b/packages/apps/human-app/frontend/src/assets/icons/work.svg deleted file mode 100644 index cd6b13c4de..0000000000 --- a/packages/apps/human-app/frontend/src/assets/icons/work.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/packages/apps/human-app/frontend/src/main.tsx b/packages/apps/human-app/frontend/src/main.tsx index bf9aaa190f..27b8e730f0 100644 --- a/packages/apps/human-app/frontend/src/main.tsx +++ b/packages/apps/human-app/frontend/src/main.tsx @@ -1,29 +1,23 @@ import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import '@/shared/i18n/i18n'; import CssBaseline from '@mui/material/CssBaseline'; import { BrowserRouter } from 'react-router-dom'; import { AuthProvider } from '@/modules/auth/context/auth-context'; import { Router } from '@/router/router'; -import '@fontsource/inter'; import '@fontsource/inter/400.css'; import '@fontsource/inter/500.css'; import '@fontsource/inter/600.css'; import '@fontsource/inter/800.css'; -import '@fontsource/roboto'; import '@fontsource/roboto/400.css'; import '@fontsource/roboto/500.css'; import { WalletConnectProvider } from '@/shared/contexts/wallet-connect'; -import { Web3AuthProvider } from '@/modules/auth-web3/context/web3-auth-context'; import { JWTExpirationCheck } from '@/shared/contexts/jwt-expiration-check'; import { ColorModeProvider } from '@/shared/contexts/color-mode'; -import { HomePageStateProvider } from '@/shared/contexts/homepage-state'; import { NotificationProvider } from '@/shared/providers/notifications-provider'; import { ModalProvider } from './shared/contexts/modal-context'; import { GlobalModal } from './shared/components/ui/modal/global-modal'; -import { UiConfigProvider } from './shared/providers/ui-config-provider'; const root = document.getElementById('root'); if (!root) throw Error('root element is undefined'); @@ -40,30 +34,20 @@ createRoot(root).render( - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/packages/apps/human-app/frontend/src/modules/auth-web3/components/wallet-connect-modal.tsx b/packages/apps/human-app/frontend/src/modules/auth-web3/components/wallet-connect-modal.tsx deleted file mode 100644 index 7f2446b2c1..0000000000 --- a/packages/apps/human-app/frontend/src/modules/auth-web3/components/wallet-connect-modal.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { t } from 'i18next'; -import { Grid, Typography } from '@mui/material'; -import { Trans } from 'react-i18next'; -import { useEffect } from 'react'; -import { Button } from '@/shared/components/ui/button'; -import { ConnectWalletBtn } from '@/shared/components/ui/connect-wallet-btn'; -import { useWalletConnect } from '@/shared/contexts/wallet-connect'; -import { breakpoints } from '@/shared/styles/breakpoints'; - -export function WalletConnectModal({ close }: Readonly<{ close: () => void }>) { - const { isConnected } = useWalletConnect(); - - useEffect(() => { - if (isConnected) { - close(); - } - // eslint-disable-next-line react-hooks/exhaustive-deps -- ... - }, [isConnected]); - - return ( - - - {t('walletConnectModal.header')} - - - - - - {t('walletConnectModal.connectBtn')} - - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/auth-web3/context/web3-auth-context.tsx b/packages/apps/human-app/frontend/src/modules/auth-web3/context/web3-auth-context.tsx deleted file mode 100644 index 72d59963b9..0000000000 --- a/packages/apps/human-app/frontend/src/modules/auth-web3/context/web3-auth-context.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { z } from 'zod'; -import { createAuthProvider } from '@/shared/contexts/generic-auth-context'; - -export enum OperatorStatus { - ACTIVE = 'active', - INACTIVE = 'inactive', -} - -const web3userDataSchema = z.object({ - user_id: z.number(), - wallet_address: z.string(), - reputation_network: z.string(), - operator_status: z.enum(OperatorStatus), - exp: z.number(), - status: z.literal('active'), -}); - -export type Web3UserData = z.infer; - -export const { AuthContext: Web3AuthContext, AuthProvider: Web3AuthProvider } = - createAuthProvider({ - authType: 'web3', - schema: web3userDataSchema, - }); diff --git a/packages/apps/human-app/frontend/src/modules/auth-web3/hooks/use-wallet-connect-modal.tsx b/packages/apps/human-app/frontend/src/modules/auth-web3/hooks/use-wallet-connect-modal.tsx deleted file mode 100644 index fca2609a2a..0000000000 --- a/packages/apps/human-app/frontend/src/modules/auth-web3/hooks/use-wallet-connect-modal.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import { useModal } from '@/shared/contexts/modal-context'; -import { WalletConnectModal } from '../components/wallet-connect-modal'; - -export function useWalletConnectModal() { - const { openModal, closeModal } = useModal(); - - return { - openModal: () => { - openModal({ - content: , - showCloseButton: false, - }); - }, - }; -} diff --git a/packages/apps/human-app/frontend/src/modules/auth-web3/hooks/use-web3-auth.tsx b/packages/apps/human-app/frontend/src/modules/auth-web3/hooks/use-web3-auth.tsx deleted file mode 100644 index ec1e6ddc07..0000000000 --- a/packages/apps/human-app/frontend/src/modules/auth-web3/hooks/use-web3-auth.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { useContext } from 'react'; -import { Web3AuthContext } from '@/modules/auth-web3/context/web3-auth-context'; - -export function useWeb3Auth() { - const context = useContext(Web3AuthContext); - - if (!context) { - throw new Error('No context for useWeb3Auth'); - } - - return context; -} diff --git a/packages/apps/human-app/frontend/src/modules/auth-web3/hooks/use-web3-authenticated-user.tsx b/packages/apps/human-app/frontend/src/modules/auth-web3/hooks/use-web3-authenticated-user.tsx deleted file mode 100644 index d8365b7312..0000000000 --- a/packages/apps/human-app/frontend/src/modules/auth-web3/hooks/use-web3-authenticated-user.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { useContext } from 'react'; -import { Web3AuthenticatedUserContext } from '@/modules/auth-web3/providers/require-web3-auth'; - -export function useWeb3AuthenticatedUser() { - const context = useContext(Web3AuthenticatedUserContext); - - if (!context) { - throw new Error( - 'Cannot use context of useWeb3AuthenticatedUser. Component is not included in web3AuthProtectedRoutes' - ); - } - - return context; -} diff --git a/packages/apps/human-app/frontend/src/modules/auth-web3/providers/require-web3-auth.tsx b/packages/apps/human-app/frontend/src/modules/auth-web3/providers/require-web3-auth.tsx deleted file mode 100644 index d17ac6a555..0000000000 --- a/packages/apps/human-app/frontend/src/modules/auth-web3/providers/require-web3-auth.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import type { ReactNode } from 'react'; -import { useLocation, Navigate } from 'react-router-dom'; -import { createContext } from 'react'; -import { routerPaths } from '@/router/router-paths'; -import { PageCardLoader } from '@/shared/components/ui/page-card'; -import { useWeb3Auth } from '@/modules/auth-web3/hooks/use-web3-auth'; -import { type AuthenticatedUserContextType } from '@/shared/contexts/generic-auth-context'; -import { type Web3UserData } from '../context/web3-auth-context'; - -export const Web3AuthenticatedUserContext = - createContext | null>(null); - -export function RequireWeb3Auth({ - children, -}: Readonly<{ children: ReactNode }>) { - const web3Auth = useWeb3Auth(); - const location = useLocation(); - - if (web3Auth.status === 'loading') { - return ; - } - - if (!web3Auth.user) { - return ( - - ); - } - - if (web3Auth.status !== 'success') { - return ( - - ); - } - - return ( - - {children} - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/auth/context/auth-context.tsx b/packages/apps/human-app/frontend/src/modules/auth/context/auth-context.tsx index 13b9001fed..41df7be2d3 100644 --- a/packages/apps/human-app/frontend/src/modules/auth/context/auth-context.tsx +++ b/packages/apps/human-app/frontend/src/modules/auth/context/auth-context.tsx @@ -11,12 +11,10 @@ const userDataSchema = z.object({ user_id: z.number(), reputation_network: z.string(), exp: z.number(), - is_stake_eligible: z.boolean(), }); export type UserData = z.infer; export const { AuthContext, AuthProvider } = createAuthProvider({ - authType: 'web2', schema: userDataSchema, }); diff --git a/packages/apps/human-app/frontend/src/modules/auth/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/auth/hooks/index.ts deleted file mode 100644 index 07e811485a..0000000000 --- a/packages/apps/human-app/frontend/src/modules/auth/hooks/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './use-auth'; -export * from './use-authenticated-user'; diff --git a/packages/apps/human-app/frontend/src/modules/auth/providers/require-auth.tsx b/packages/apps/human-app/frontend/src/modules/auth/providers/require-auth.tsx index 2ef94b7460..2c0717fe8b 100644 --- a/packages/apps/human-app/frontend/src/modules/auth/providers/require-auth.tsx +++ b/packages/apps/human-app/frontend/src/modules/auth/providers/require-auth.tsx @@ -1,18 +1,20 @@ -import type { ReactNode } from 'react'; +import { createContext, type ReactNode } from 'react'; import { useLocation, Navigate } from 'react-router-dom'; -import { createContext } from 'react'; + import { useAuth } from '@/modules/auth/hooks/use-auth'; import { routerPaths } from '@/router/router-paths'; import { PageCardLoader } from '@/shared/components/ui/page-card'; import { type AuthenticatedUserContextType } from '@/shared/contexts/generic-auth-context'; import { type UserData } from '../context/auth-context'; +import { useIsUserVerified } from '@/shared/hooks'; export const AuthenticatedUserContext = createContext | null>(null); -export function RequireAuth({ children }: Readonly<{ children: ReactNode }>) { +export function RequireAuth({ children }: { children: ReactNode }) { const auth = useAuth(); const location = useLocation(); + const isUserVerified = useIsUserVerified(); if (auth.status === 'loading') { return ; @@ -29,11 +31,15 @@ export function RequireAuth({ children }: Readonly<{ children: ReactNode }>) { ); } + if (!isUserVerified) { + return ; + } + return ( {children} diff --git a/packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx b/packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx index a1096e1d3d..6128ff6366 100644 --- a/packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx +++ b/packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx @@ -4,7 +4,6 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { env } from '@/shared/env'; import { useColorMode } from '@/shared/contexts/color-mode'; -import { useWorkerIdentityVerificationStatus } from '@/modules/worker/profile/hooks'; import { useProposalQuery } from '../hooks/use-proposal-query'; import { formatCountdown } from '../../../shared/utils/time'; import { type ProposalResponse } from '../services/governance.service'; @@ -17,10 +16,10 @@ function getProposalStatus(proposal: ProposalResponse): ProposalStatus { if (voteStart <= now && now < voteEnd) return 'active'; return 'pending'; } + export function GovernanceBanner() { const { t } = useTranslation(); const { data: proposal, isLoading, isError } = useProposalQuery(); - const { isVerificationCompleted } = useWorkerIdentityVerificationStatus(); const { colorPalette } = useColorMode(); const { text, background } = colorPalette.banner; const [timeRemaining, setTimeRemaining] = useState('00:00:00'); @@ -41,7 +40,7 @@ export function GovernanceBanner() { }; }, [proposal]); - if (!isVerificationCompleted || isLoading || isError || !proposal) { + if (isLoading || isError || !proposal) { return null; } @@ -53,45 +52,49 @@ export function GovernanceBanner() { return ( {/* Left side: Countdown & "X votes" */} - + - + {status === 'pending' ? t('governance.timeToStart', 'Voting starts in') : t('governance.timeToReveal', 'Time to reveal vote')} : - + {timeRemaining} {status === 'active' && ( {totalVotes} {t('governance.votes', 'votes')} @@ -100,19 +103,24 @@ export function GovernanceBanner() { {/* Right side: "More details" link */} {t('governance.moreDetails', 'More details')} → diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/chat.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/chat.tsx index 54706e36d5..af26126045 100644 --- a/packages/apps/human-app/frontend/src/modules/homepage/components/chat.tsx +++ b/packages/apps/human-app/frontend/src/modules/homepage/components/chat.tsx @@ -13,21 +13,27 @@ export function Chat({ const chatElement = document.querySelector('[data-id="zsalesiq"]') as | HTMLElement | undefined; - if (chatElement) { - chatElement.style.opacity = displayChatIcon ? '1' : '0'; - chatElement.style.pointerEvents = displayChatIcon ? 'auto' : 'none'; - chatElement.childNodes.forEach((element, idx) => { - if (element instanceof HTMLElement) { - element.style.right = isMobile ? '16px' : '24px'; - if (idx === 1) { - element.style.bottom = isMobile ? '16px' : '20px'; - } - } - }); - return true; + if (!chatElement) { + return false; } - return false; + chatElement.style.opacity = '1'; + chatElement.style.pointerEvents = 'auto'; + + window.$zoho?.salesiq?.floatbutton?.visible?.( + displayChatIcon ? 'show' : 'hide' + ); + + chatElement.childNodes.forEach((element, idx) => { + if (element instanceof HTMLElement) { + element.style.right = isMobile ? '16px' : '24px'; + if (idx === 1) { + element.style.bottom = isMobile ? '80px' : '20px'; + } + } + }); + + return true; }; if (applyVisibility()) { diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/choose-sign-up-account-type.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/choose-sign-up-account-type.tsx deleted file mode 100644 index a0c2415e16..0000000000 --- a/packages/apps/human-app/frontend/src/modules/homepage/components/choose-sign-up-account-type.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Grid, Typography } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { PageCard } from '@/shared/components/ui/page-card'; -import type { HomePageStageType } from '@/modules/homepage/views/home.page'; -import { useHomePageState } from '@/shared/contexts/homepage-state'; -import { WorkerSignUp } from './worker-sign-up'; -import { OperatorSignUp } from './operator-sign-up'; - -interface ChooseSignUpAccountType { - setStage: (step: HomePageStageType) => void; -} - -export function ChooseSignUpAccountType() { - const { setPageView } = useHomePageState(); - const { t } = useTranslation(); - - const backToWelcomeStage = () => { - setPageView('welcome'); - }; - - return ( - {t('homepage.welcome')} 👋} - > - - - {t('homepage.howWillUse')} - - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/home-container.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/home-container.tsx deleted file mode 100644 index 8450f2b27a..0000000000 --- a/packages/apps/human-app/frontend/src/modules/homepage/components/home-container.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Container } from '@mui/material'; -import { useEffect } from 'react'; -import { useBackgroundContext } from '@/shared/contexts/background'; -import { useHomePageState } from '@/shared/contexts/homepage-state'; -import { Welcome } from './welcome'; -import { ChooseSignUpAccountType } from './choose-sign-up-account-type'; - -export function HomeContainer() { - const { pageView } = useHomePageState(); - const { setWhiteBackground, setGrayBackground } = useBackgroundContext(); - - useEffect(() => { - if (pageView === 'chooseSignUpAccountType') { - setGrayBackground(); - } else { - setWhiteBackground(); - } - }, [setGrayBackground, setWhiteBackground, pageView]); - - return ( - - {pageView === 'welcome' && } - {pageView === 'chooseSignUpAccountType' && } - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/index.ts b/packages/apps/human-app/frontend/src/modules/homepage/components/index.ts deleted file mode 100644 index 8a804abdf7..0000000000 --- a/packages/apps/human-app/frontend/src/modules/homepage/components/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './home-container'; diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/logo-section.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/logo-section.tsx index 6116bb99a3..0c7089ebba 100644 --- a/packages/apps/human-app/frontend/src/modules/homepage/components/logo-section.tsx +++ b/packages/apps/human-app/frontend/src/modules/homepage/components/logo-section.tsx @@ -1,69 +1,40 @@ import { Grid, Stack, Typography } from '@mui/material'; import { useTranslation } from 'react-i18next'; -import { - MobileHomeIcons, - HomepageWorkIcon, - HomepageUserIcon, - HomepageLogoIcon, -} from '@/shared/components/ui/icons'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; export function LogoSection() { const { t } = useTranslation(); const logoText: string = t('homepage.humanApp'); const logoTextSplit: string[] = logoText.split(' '); - const isMobile = useIsMobile('lg'); return ( - - {isMobile ? ( - - - - ) : ( - - - - - - - - - - - - )} + {logoTextSplit[0]} - + {logoTextSplit[1]} {t('homepage.completeJobs')} diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/operator-sign-in.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/operator-sign-in.tsx deleted file mode 100644 index 2c215be780..0000000000 --- a/packages/apps/human-app/frontend/src/modules/homepage/components/operator-sign-in.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { t } from 'i18next'; -import { useEffect, useRef } from 'react'; -import { Link } from 'react-router-dom'; -import Snackbar from '@mui/material/Snackbar'; -import { Button } from '@/shared/components/ui/button'; -import { useWalletConnect } from '@/shared/contexts/wallet-connect'; -import { useWeb3Auth } from '@/modules/auth-web3/hooks/use-web3-auth'; -import { routerPaths } from '@/router/router-paths'; -import { getErrorMessageForError } from '@/shared/errors'; -import { useWeb3SignIn } from '../hooks'; - -export function OperatorSignIn() { - const { isConnected, openModal, address } = useWalletConnect(); - const { - mutate: signInMutation, - isError: isSignInMutationError, - error: signInMutationError, - } = useWeb3SignIn(); - const { user, signOut } = useWeb3Auth(); - const modalWasOpened = useRef(false); - - useEffect(() => { - if (isConnected && modalWasOpened.current) { - signInMutation(); - } - }, [address, isConnected, signInMutation]); - - const getSnackBar = () => { - return ( - - ); - }; - - if (user) { - return ( - <> - - {getSnackBar()} - - ); - } - - if (!isConnected) { - return ( - <> - - {getSnackBar()} - - ); - } - - return ( - <> - - {getSnackBar()} - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/operator-sign-up.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/operator-sign-up.tsx deleted file mode 100644 index 7ec945c879..0000000000 --- a/packages/apps/human-app/frontend/src/modules/homepage/components/operator-sign-up.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import { Grid, Typography, List, ListItemText, Button } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { Link } from 'react-router-dom'; -import { routerPaths } from '@/router/router-paths'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette'; - -export function OperatorSignUp() { - const { colorPalette, isDarkMode } = useColorMode(); - const { t } = useTranslation(); - const isMobile = useIsMobile('lg'); - - return ( - -
- - {t('homepage.joinAsOperator')} - - - - - -
- - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/sign-in-section.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/sign-in-section.tsx index 0c33122c08..09b30c6cbb 100644 --- a/packages/apps/human-app/frontend/src/modules/homepage/components/sign-in-section.tsx +++ b/packages/apps/human-app/frontend/src/modules/homepage/components/sign-in-section.tsx @@ -1,42 +1,46 @@ -import { Paper, Button, Divider } from '@mui/material'; +import { Paper, Button } from '@mui/material'; +import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { useColorMode } from '@/shared/contexts/color-mode'; -import { useHomePageState } from '@/shared/contexts/homepage-state'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { OperatorSignIn } from './operator-sign-in'; -import { WorkerSignIn } from './worker-sign-in'; +import { routerPaths } from '@/router/router-paths'; export function SignInSection() { - const isMobile = useIsMobile('lg'); const { colorPalette } = useColorMode(); - const { setPageView } = useHomePageState(); const { t } = useTranslation(); return ( - - - + ); } diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/welcome.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/welcome.tsx deleted file mode 100644 index 2737034284..0000000000 --- a/packages/apps/human-app/frontend/src/modules/homepage/components/welcome.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Grid } from '@mui/material'; -import { useEffect } from 'react'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { useBackgroundContext } from '@/shared/contexts/background'; -import { SignInSection } from './sign-in-section'; -import { LogoSection } from './logo-section'; - -export function Welcome() { - const { isDarkMode } = useColorMode(); - const { setWhiteBackground } = useBackgroundContext(); - const isMobile = useIsMobile('lg'); - - useEffect(() => { - if (!isDarkMode) { - setWhiteBackground(); - } - }, [isDarkMode, setWhiteBackground]); - - return ( - - - - - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/worker-sign-in.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/worker-sign-in.tsx deleted file mode 100644 index 780c25cb38..0000000000 --- a/packages/apps/human-app/frontend/src/modules/homepage/components/worker-sign-in.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { t } from 'i18next'; -import { Link } from 'react-router-dom'; -import { Button } from '@/shared/components/ui/button'; -import { routerPaths } from '@/router/router-paths'; - -export function WorkerSignIn() { - return ( - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/homepage/components/worker-sign-up.tsx b/packages/apps/human-app/frontend/src/modules/homepage/components/worker-sign-up.tsx deleted file mode 100644 index 8ec2dcab12..0000000000 --- a/packages/apps/human-app/frontend/src/modules/homepage/components/worker-sign-up.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { Grid, Typography, List, ListItemText, Button } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { Link } from 'react-router-dom'; -import { routerPaths } from '@/router/router-paths'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette'; - -export function WorkerSignUp() { - const { colorPalette, isDarkMode } = useColorMode(); - const { t } = useTranslation(); - const isMobile = useIsMobile('lg'); - - return ( - - -
- - {t('homepage.iWantToEarn')} - - - - - -
- - - -
-
- ); -} diff --git a/packages/apps/human-app/frontend/src/modules/homepage/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/homepage/hooks/index.ts deleted file mode 100644 index f8ca47e636..0000000000 --- a/packages/apps/human-app/frontend/src/modules/homepage/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './use-web3-signin'; diff --git a/packages/apps/human-app/frontend/src/modules/homepage/hooks/use-web3-signin.ts b/packages/apps/human-app/frontend/src/modules/homepage/hooks/use-web3-signin.ts deleted file mode 100644 index b5cfa38ab2..0000000000 --- a/packages/apps/human-app/frontend/src/modules/homepage/hooks/use-web3-signin.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { z } from 'zod'; -import { useNavigate } from 'react-router-dom'; -import { useWeb3Auth } from '@/modules/auth-web3/hooks/use-web3-auth'; -import { routerPaths } from '@/router/router-paths'; -import { useWalletConnect } from '@/shared/contexts/wallet-connect'; -import { usePrepareSignature } from '@/shared/hooks'; -import { PrepareSignatureType } from '@/shared/services/signature.service'; -import * as homepageService from '../services/homepage.service'; - -export const web3SignInSuccessResponseSchema = z.object({ - access_token: z.string(), - refresh_token: z.string(), -}); - -export type Web3SignInSuccessResponse = z.infer< - typeof web3SignInSuccessResponseSchema ->; - -export function useWeb3SignIn() { - const { address, chainId, signMessage } = useWalletConnect(); - const { signIn } = useWeb3Auth(); - const navigate = useNavigate(); - const { prepareSignature } = usePrepareSignature( - PrepareSignatureType.SIGN_IN - ); - - return useMutation({ - mutationFn: async () => { - const data = await prepareSignature(); - - const signature = await signMessage(JSON.stringify(data)); - - if (!signature || !address) { - throw new Error('Failed to sign message: missing arguments.'); - } - - return homepageService.web3SignIn({ - address, - signature, - }); - }, - onSuccess: (successResponse) => { - signIn(successResponse); - navigate(routerPaths.operator.profile); - }, - - mutationKey: ['web3SignIn', address, chainId], - }); -} diff --git a/packages/apps/human-app/frontend/src/modules/homepage/services/homepage.service.ts b/packages/apps/human-app/frontend/src/modules/homepage/services/homepage.service.ts deleted file mode 100644 index 98c44f091a..0000000000 --- a/packages/apps/human-app/frontend/src/modules/homepage/services/homepage.service.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { ApiClientError, humanAppApiClient } from '@/api'; -import { type Web3SignInSuccessResponse } from '../hooks'; - -const apiPaths = { - web3Auth: { - signIn: '/auth/web3/signin', - }, -}; - -async function web3SignIn(data: { signature: string; address: string }) { - try { - const result = await humanAppApiClient.post( - apiPaths.web3Auth.signIn, - { - body: data, - } - ); - return result; - } catch (error) { - if (error instanceof ApiClientError) { - throw error; - } - - throw new Error('Failed to log in using web3.'); - } -} - -export { web3SignIn }; diff --git a/packages/apps/human-app/frontend/src/modules/homepage/views/home.page.tsx b/packages/apps/human-app/frontend/src/modules/homepage/views/home.page.tsx index 7176f2fa77..c85d49d090 100644 --- a/packages/apps/human-app/frontend/src/modules/homepage/views/home.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/homepage/views/home.page.tsx @@ -1,53 +1,44 @@ -import Box from '@mui/material/Box'; -import { Paper } from '@mui/material'; +import { Box, Container, Grid, Paper } from '@mui/material'; import { Navigate } from 'react-router-dom'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { useWeb3Auth } from '@/modules/auth-web3/hooks/use-web3-auth'; -import { useAuth } from '@/modules/auth/hooks/use-auth'; + import { routerPaths } from '@/router/router-paths'; import { useColorMode } from '@/shared/contexts/color-mode'; -import { HomeContainer } from '@/modules/homepage/components'; -import { useHomePageState } from '@/shared/contexts/homepage-state'; - -export type HomePageStageType = 'welcome' | 'chooseSignUpAccountType'; +import { LogoSection } from '../components/logo-section'; +import { SignInSection } from '../components/sign-in-section'; +import { useIsUserVerified } from '@/shared/hooks'; export function HomePage() { - const { colorPalette, isDarkMode } = useColorMode(); - const { pageView } = useHomePageState(); - const isMobile = useIsMobile(); - const { user: worker } = useAuth(); - const { user: operator } = useWeb3Auth(); + const { colorPalette } = useColorMode(); + const isUserVerified = useIsUserVerified(); - if (worker) { - return ; - } - if (operator) { - return ; + if (isUserVerified) { + return ; } - const paperBackgroundColor = (() => { - if (isDarkMode) { - return colorPalette.paper.main; - } - - return colorPalette.backgroundColor; - })(); - return ( - + - + + + + + + + + + + ); diff --git a/packages/apps/human-app/frontend/src/modules/operator/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/operator/hooks/index.ts deleted file mode 100644 index 6a0e8ed3e5..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './use-get-keys'; diff --git a/packages/apps/human-app/frontend/src/modules/operator/hooks/use-get-keys.ts b/packages/apps/human-app/frontend/src/modules/operator/hooks/use-get-keys.ts deleted file mode 100644 index 166c232129..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/hooks/use-get-keys.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { z } from 'zod'; -import { useConnectedWallet } from '@/shared/contexts/wallet-connect'; -import { ethKVStoreGetKeys } from '@/modules/smart-contracts/EthKVStore/eth-kv-store-get-keys'; -import { getContractAddress } from '@/modules/smart-contracts/get-contract-address'; -import { EthKVStoreKeys } from '@/modules/smart-contracts/EthKVStore/config'; - -export const getEthKVStoreValuesSuccessSchema = z.object({ - [EthKVStoreKeys.PublicKey]: z.string().optional(), - [EthKVStoreKeys.Url]: z.string().optional(), - [EthKVStoreKeys.WebhookUrl]: z.string().optional(), - [EthKVStoreKeys.Role]: z.string().optional(), - [EthKVStoreKeys.Fee]: z.string().optional(), - [EthKVStoreKeys.JobTypes]: z - .string() - .transform((jobTypes) => { - if (!jobTypes) return undefined; - return jobTypes.split(','); - }) - .optional(), -}); - -export type GetEthKVStoreValuesSuccessResponse = z.infer< - typeof getEthKVStoreValuesSuccessSchema ->; - -export function useGetKeys() { - const { - chainId, - address, - web3ProviderMutation: { data }, - } = useConnectedWallet(); - - return useQuery({ - queryFn: async () => { - const contractAddress = getContractAddress({ - contractName: 'EthKVStore', - }); - const result = await ethKVStoreGetKeys({ - accountAddress: address, - contractAddress, - chainId, - signer: data?.signer, - }); - - const validData = getEthKVStoreValuesSuccessSchema.parse(result); - return validData; - }, - queryKey: [chainId, address, data?.signer], - }); -} diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/components/index.ts b/packages/apps/human-app/frontend/src/modules/operator/profile/components/index.ts deleted file mode 100644 index b52f63e631..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/profile/components/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './profile-disable-button'; -export * from './profile-enable-button'; -export * from './operator-info'; -export * from './operator-stats'; diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/components/operator-info.tsx b/packages/apps/human-app/frontend/src/modules/operator/profile/components/operator-info.tsx deleted file mode 100644 index 36dfb88519..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/profile/components/operator-info.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { Paper, Typography, Stack, List, Grid } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { OperatorStatus } from '@/modules/auth-web3/context/web3-auth-context'; -import { useWeb3AuthenticatedUser } from '@/modules/auth-web3/hooks/use-web3-authenticated-user'; -import { CheckmarkIcon, LockerIcon } from '@/shared/components/ui/icons'; -import { ProfileListItem } from '@/shared/components/ui/profile'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { type GetEthKVStoreValuesSuccessResponse } from '../../hooks/use-get-keys'; -import { ProfileDisableButton } from './profile-disable-button'; -import { ProfileEnableButton } from './profile-enable-button'; - -export function OperatorInfo({ - keysData, -}: Readonly<{ keysData: GetEthKVStoreValuesSuccessResponse }>) { - const { colorPalette } = useColorMode(); - const { t } = useTranslation(); - const { user } = useWeb3AuthenticatedUser(); - const isMobile = useIsMobile('lg'); - - const isOperatorActive = user.operator_status === OperatorStatus.ACTIVE; - - return ( - - - {t('operator.profile.about.header')} - - - - - - - {t('operator.profile.about.status.statusHeader')} - - {isOperatorActive ? ( - - {t('operator.profile.about.status.statusActivated')} - - - ) : ( - - {t('operator.profile.about.status.statusDeactivated')} - - - )} -
- {isOperatorActive ? ( - - ) : ( - - )} -
-
- - - - -
-
-
- ); -} diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/components/operator-stats.tsx b/packages/apps/human-app/frontend/src/modules/operator/profile/components/operator-stats.tsx deleted file mode 100644 index a0a8b06723..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/profile/components/operator-stats.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { Paper, Typography, Stack, List } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { ProfileListItem } from '@/shared/components/ui/profile'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { type OperatorStatsResponse } from '../types'; - -export function OperatorStats({ - statsData, -}: Readonly<{ - statsData: OperatorStatsResponse; -}>) { - const isMobile = useIsMobile('lg'); - const { colorPalette } = useColorMode(); - const { t } = useTranslation(); - - return ( - - - {t('operator.profile.statistics.header')} - - - - - - - - - - - - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/components/profile-disable-button.tsx b/packages/apps/human-app/frontend/src/modules/operator/profile/components/profile-disable-button.tsx deleted file mode 100644 index fd6798bd39..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/profile/components/profile-disable-button.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { t } from 'i18next'; -import { useEffect } from 'react'; -import { useConnectedWallet } from '@/shared/contexts/wallet-connect'; -import { Button } from '@/shared/components/ui/button'; -import { - TopNotificationType, - useNotification, - usePrepareSignature, -} from '@/shared/hooks'; -import { PrepareSignatureType } from '@/shared/services/signature.service'; -import { useDisableWeb3Operator } from '../hooks'; - -export function ProfileDisableButton() { - const { signMessage } = useConnectedWallet(); - const { - prepareSignature, - isPending: isSignatureDataPending, - error: signatureDataError, - } = usePrepareSignature(PrepareSignatureType.DISABLE_OPERATOR); - - const { - mutate: disableOperatorMutation, - error: disableOperatorError, - isPending: isDisableOperatorPending, - } = useDisableWeb3Operator(); - - const { showNotification } = useNotification(); - - useEffect(() => { - if (Boolean(signatureDataError) || Boolean(disableOperatorError)) { - showNotification({ - message: t('operator.profile.disable.cannotDisable'), - type: TopNotificationType.WARNING, - }); - } - }, [signatureDataError, disableOperatorError, showNotification]); - - const disableOperator = async () => { - const signaturePayload = await prepareSignature(); - const signature = await signMessage(JSON.stringify(signaturePayload)); - disableOperatorMutation({ signature: signature ?? '' }); - }; - - return ( - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/components/profile-enable-button.tsx b/packages/apps/human-app/frontend/src/modules/operator/profile/components/profile-enable-button.tsx deleted file mode 100644 index 7773f5577f..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/profile/components/profile-enable-button.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { t } from 'i18next'; -import { useEffect } from 'react'; -import { useConnectedWallet } from '@/shared/contexts/wallet-connect'; -import { Button } from '@/shared/components/ui/button'; -import { - TopNotificationType, - useNotification, - usePrepareSignature, -} from '@/shared/hooks'; -import { PrepareSignatureType } from '@/shared/services/signature.service'; -import { useEnableWeb3Operator } from '../hooks'; - -export function ProfileEnableButton() { - const { signMessage } = useConnectedWallet(); - const { - prepareSignature, - error: signatureDataError, - isPending: isSignatureDataPending, - } = usePrepareSignature(PrepareSignatureType.ENABLE_OPERATOR); - - const { - mutate: enableOperatorMutation, - error: enableOperatorError, - isPending: isEnableOperatorPending, - } = useEnableWeb3Operator(); - - const { showNotification } = useNotification(); - - useEffect(() => { - if (Boolean(signatureDataError) || Boolean(enableOperatorError)) { - showNotification({ - message: t('operator.profile.activate.cannotActivate'), - type: TopNotificationType.WARNING, - }); - } - }, [signatureDataError, enableOperatorError, showNotification]); - - const enableOperator = async () => { - const signaturePayload = await prepareSignature(); - const signature = await signMessage(JSON.stringify(signaturePayload)); - enableOperatorMutation({ signature: signature ?? '' }); - }; - - return ( - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/index.ts deleted file mode 100644 index b95a7fdb88..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './use-get-stats'; -export * from './use-disable-operator'; -export * from './use-enable-operator'; diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/use-disable-operator.ts b/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/use-disable-operator.ts deleted file mode 100644 index 696d2321ac..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/use-disable-operator.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { useConnectedWallet } from '@/shared/contexts/wallet-connect'; -import { OperatorStatus } from '@/modules/auth-web3/context/web3-auth-context'; -import { useWeb3AuthenticatedUser } from '@/modules/auth-web3/hooks/use-web3-authenticated-user'; -import { authService } from '@/api'; -import * as operatorProfileService from '../services/profile.service'; - -export function useDisableWeb3Operator() { - const { address, chainId } = useConnectedWallet(); - const { updateUserData } = useWeb3AuthenticatedUser(); - return useMutation({ - mutationFn: async ({ signature }: { signature: string }) => { - await operatorProfileService.disableOperator(signature); - - await authService.refreshAccessToken(); - - updateUserData({ operator_status: OperatorStatus.INACTIVE }); - }, - mutationKey: ['disableOperator', address, chainId], - }); -} diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/use-enable-operator.ts b/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/use-enable-operator.ts deleted file mode 100644 index 3385616925..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/use-enable-operator.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { useConnectedWallet } from '@/shared/contexts/wallet-connect'; -import { useWeb3AuthenticatedUser } from '@/modules/auth-web3/hooks/use-web3-authenticated-user'; -import { OperatorStatus } from '@/modules/auth-web3/context/web3-auth-context'; -import { authService } from '@/api'; -import * as operatorProfileService from '../services/profile.service'; - -export function useEnableWeb3Operator() { - const { address, chainId } = useConnectedWallet(); - const { updateUserData } = useWeb3AuthenticatedUser(); - return useMutation({ - mutationFn: async ({ signature }: { signature: string }) => { - await operatorProfileService.enableOperator(signature); - - await authService.refreshAccessToken(); - - updateUserData({ operator_status: OperatorStatus.ACTIVE }); - }, - mutationKey: ['enableOperator', address, chainId], - }); -} diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/use-get-stats.ts b/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/use-get-stats.ts deleted file mode 100644 index e82b88630a..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/use-get-stats.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { useGetKeys } from '@/modules/operator/hooks/use-get-keys'; -import * as operatorProfileService from '../services/profile.service'; - -export const failedResponse = { - workers_total: '-', - assignments_completed: '-', - assignments_expired: '-', - assignments_rejected: '-', - escrows_processed: '-', - escrows_active: '-', - escrows_cancelled: '-', -}; - -export function useGetOperatorStats() { - const { data: keysData } = useGetKeys(); - - return useQuery({ - queryFn: async () => { - if (!keysData?.url) { - return failedResponse; - } - return operatorProfileService - .getStats(keysData.url) - .catch(() => failedResponse); - }, - queryKey: ['getOperatorStats', keysData?.url], - }); -} diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/index.ts b/packages/apps/human-app/frontend/src/modules/operator/profile/index.ts deleted file mode 100644 index 30c1db5b64..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/profile/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './profile.page'; diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/profile.page.tsx b/packages/apps/human-app/frontend/src/modules/operator/profile/profile.page.tsx deleted file mode 100644 index 6e0e610130..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/profile/profile.page.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { useEffect } from 'react'; -import { Grid } from '@mui/material'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { useGetKeys } from '@/modules/operator/hooks/use-get-keys'; -import { - PageCardError, - PageCardLoader, -} from '@/shared/components/ui/page-card'; -import { getErrorMessageForError } from '@/shared/errors'; -import { useGetOperatorStats } from './hooks'; -import { OperatorInfo, OperatorStats } from './components'; - -export function OperatorProfilePage() { - const isMobile = useIsMobile('lg'); - const { - data: keysData, - error: keysError, - isError: isKeysError, - isPending: isKeysDataPending, - } = useGetKeys(); - - const { - data: statsData, - error: statsError, - isError: isStatsError, - isPending: isStatsPending, - refetch: refetchStats, - } = useGetOperatorStats(); - - useEffect(() => { - if (keysData?.url) { - void refetchStats(); - } - }, [keysData?.url, refetchStats]); - - if (isKeysDataPending || isStatsPending) { - return ; - } - - if (isKeysError || isStatsError) { - return ( - - ); - } - - return ( - - - - - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/schemas.ts b/packages/apps/human-app/frontend/src/modules/operator/profile/schemas.ts deleted file mode 100644 index cfdd3dbb19..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/profile/schemas.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { z } from 'zod'; - -export const operatorStatsSuccessResponseSchema = z.object({ - workers_total: z.number(), - assignments_completed: z.number(), - assignments_expired: z.number(), - assignments_rejected: z.number(), - escrows_processed: z.number(), - escrows_active: z.number(), - escrows_cancelled: z.number(), -}); diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/services/profile.service.ts b/packages/apps/human-app/frontend/src/modules/operator/profile/services/profile.service.ts deleted file mode 100644 index 3292b71439..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/profile/services/profile.service.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { - ApiClientError, - authorizedHumanAppApiClient, - HttpApiClient, -} from '@/api'; -import { type OperatorStatsSuccessResponse } from '../types'; -import { operatorStatsSuccessResponseSchema } from '../schemas'; - -const apiPaths = { - enableOperator: '/enable-operator', - disableOperator: '/disable-operator', - stats: '/stats', -}; - -async function enableOperator(signature: string) { - try { - await authorizedHumanAppApiClient.post(apiPaths.enableOperator, { - body: { signature }, - }); - } catch (error) { - if (error instanceof ApiClientError) { - throw error; - } - - throw new Error('Failed to enable operator'); - } -} - -async function disableOperator(signature: string) { - try { - await authorizedHumanAppApiClient.post(apiPaths.disableOperator, { - body: { signature }, - }); - } catch (error) { - if (error instanceof ApiClientError) { - throw error; - } - - throw new Error('Failed to disable operator'); - } -} - -async function getStats(statsBaseUrl: string) { - const httpClient = new HttpApiClient(statsBaseUrl); - - try { - const result = await httpClient.get( - apiPaths.stats, - { - successSchema: operatorStatsSuccessResponseSchema, - } - ); - - return result; - } catch { - throw new Error('Failed to get stats'); - } -} - -export { enableOperator, disableOperator, getStats }; diff --git a/packages/apps/human-app/frontend/src/modules/operator/profile/types.ts b/packages/apps/human-app/frontend/src/modules/operator/profile/types.ts deleted file mode 100644 index 971dd3a0ae..0000000000 --- a/packages/apps/human-app/frontend/src/modules/operator/profile/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { type z } from 'zod'; -import { type failedResponse } from './hooks'; -import { type operatorStatsSuccessResponseSchema } from './schemas'; - -export type OperatorStatsSuccessResponse = z.infer< - typeof operatorStatsSuccessResponseSchema ->; - -type OperatorStatsFailedResponse = typeof failedResponse; - -export type OperatorStatsResponse = - | OperatorStatsSuccessResponse - | OperatorStatsFailedResponse; diff --git a/packages/apps/human-app/frontend/src/modules/signin/worker/index.ts b/packages/apps/human-app/frontend/src/modules/signin/worker/index.ts index b76ee6a475..33eedcbea5 100644 --- a/packages/apps/human-app/frontend/src/modules/signin/worker/index.ts +++ b/packages/apps/human-app/frontend/src/modules/signin/worker/index.ts @@ -1 +1,2 @@ export * from './sign-in.page'; +export * from './verify-user.page'; diff --git a/packages/apps/human-app/frontend/src/modules/signin/worker/sign-in-form.tsx b/packages/apps/human-app/frontend/src/modules/signin/worker/sign-in-form.tsx index 5aeca3562a..181d9e9192 100644 --- a/packages/apps/human-app/frontend/src/modules/signin/worker/sign-in-form.tsx +++ b/packages/apps/human-app/frontend/src/modules/signin/worker/sign-in-form.tsx @@ -1,14 +1,18 @@ +import { type SubmitEvent } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; -import { Box, Grid, Typography } from '@mui/material'; +import { Box, Divider, Grid, Link as MuiLink, Typography } from '@mui/material'; import { zodResolver } from '@hookform/resolvers/zod'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; + import { Input } from '@/shared/components/data-entry/input'; import { Button } from '@/shared/components/ui/button'; -import { Password } from '@/shared/components/data-entry/password/password'; -import { routerPaths } from '@/router/router-paths'; +import { Password } from '@/shared/components/data-entry/password'; +import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode'; import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; import { HCaptchaForm } from '@/shared/components/hcaptcha'; +import { routerPaths } from '@/router/router-paths'; + import { type SignInDto, signInDtoSchema } from './schemas'; interface SignInFormProps { @@ -23,8 +27,9 @@ export function SignInForm({ error, isLoading, resetMutation, -}: Readonly) { +}: SignInFormProps) { const { t } = useTranslation(); + const { colorPalette } = useColorMode(); const methods = useForm({ defaultValues: { @@ -37,14 +42,14 @@ export function SignInForm({ useResetMutationErrors(methods.watch, resetMutation); - const handleSubmit = (event: React.FormEvent) => { + const handleSubmit = (event: SubmitEvent) => { void methods.handleSubmit(onSubmit)(event); }; return (
- + - - - - {t('worker.signInForm.forgotPassword')} - - + + + {t('worker.signInForm.forgotPassword')} + + + + + {t('worker.signInForm.or')} + + + *': { + display: 'inline-flex', + }, + }} + > + + {t('worker.signInForm.dontHaveAccount')} + {' '} + + {t('worker.signInForm.signUp')} + +
diff --git a/packages/apps/human-app/frontend/src/modules/signin/worker/sign-in.page.tsx b/packages/apps/human-app/frontend/src/modules/signin/worker/sign-in.page.tsx index f7f216dc4d..ea0b529ba9 100644 --- a/packages/apps/human-app/frontend/src/modules/signin/worker/sign-in.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/signin/worker/sign-in.page.tsx @@ -1,11 +1,24 @@ +import { useEffect } from 'react'; import { t } from 'i18next'; -import { PageCard } from '@/shared/components/ui/page-card'; -import { Alert } from '@/shared/components/ui/alert'; +import { useNavigate } from 'react-router-dom'; +import { Box, Grid, Paper, Typography } from '@mui/material'; import { getErrorMessageForError } from '@/shared/errors'; import { ApiClientError } from '@/api'; import { useSignIn } from './use-sign-in'; import { SignInForm } from './sign-in-form'; +import signInImage from '@/assets/background-images/signin-background.png'; +import { + TopNotificationType, + useNotification, +} from '@/shared/hooks/use-notification'; +import { BackButton } from '@/shared/components/ui/page-card/back-button'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { useAuth } from '@/modules/auth/hooks/use-auth'; +import { routerPaths } from '@/router/router-paths'; +import { useIsUserVerified } from '@/shared/hooks'; +import { browserAuthProvider } from '@/shared/contexts/browser-auth-provider'; + function formattedSignInErrorMessage( unknownError: unknown ): string | undefined { @@ -14,26 +27,140 @@ function formattedSignInErrorMessage( } } -export function SignInWorkerPage() { +export function SignInPage() { + const { showNotification } = useNotification(); + const navigate = useNavigate(); + const { user, status, signOut } = useAuth(); + const { colorPalette } = useColorMode(); + const isUserVerified = useIsUserVerified(); + const { signIn, error, isError, isLoading, reset } = useSignIn(); + useEffect(() => { + if (isError) { + const message = getErrorMessageForError( + error, + formattedSignInErrorMessage + ); + showNotification({ + message, + type: TopNotificationType.ERROR, + }); + } + }, [isError, error, showNotification]); + + useEffect(() => { + if (status === 'loading' || !user) { + return; + } + + if (!browserAuthProvider.getAccessToken()) { + signOut({ throwExpirationModal: false }); + return; + } + + if (user.status === 'pending' && !user.kyc_status) { + navigate(routerPaths.verifyEmail, { + state: { routerState: { email: user.email } }, + }); + return; + } + + if (isUserVerified) { + navigate(routerPaths.profile); + return; + } + + navigate(routerPaths.verifyUser); + }, [user, status, signOut, navigate, isUserVerified]); + + const handleBackButton = () => { + navigate(-1); + }; + return ( - - {getErrorMessageForError(error, formattedSignInErrorMessage)} - - ) : undefined - } + - - + + + + + + + + + {t('worker.signInForm.title')} + + + + + + ); } diff --git a/packages/apps/human-app/frontend/src/modules/signin/worker/use-sign-in-mutation.ts b/packages/apps/human-app/frontend/src/modules/signin/worker/use-sign-in-mutation.ts index 80c11c8e6e..cf75008640 100644 --- a/packages/apps/human-app/frontend/src/modules/signin/worker/use-sign-in-mutation.ts +++ b/packages/apps/human-app/frontend/src/modules/signin/worker/use-sign-in-mutation.ts @@ -1,8 +1,7 @@ import { useMutation } from '@tanstack/react-query'; -import { useNavigate } from 'react-router-dom'; -import { routerPaths } from '@/router/router-paths'; import { env } from '@/shared/env'; import { AuthService, HttpApiClient } from '@/api'; +import { useAuth } from '@/modules/auth/hooks/use-auth'; import { type SignInDto } from './schemas'; function signInMutationFn(data: SignInDto) { @@ -13,13 +12,12 @@ function signInMutationFn(data: SignInDto) { } export function useSignInMutation() { - const navigate = useNavigate(); + const { signIn } = useAuth(); return useMutation({ mutationFn: signInMutationFn, - onSuccess: () => { - navigate(routerPaths.worker.profile); - window.location.reload(); + onSuccess: (successResponse) => { + signIn(successResponse); }, }); } diff --git a/packages/apps/human-app/frontend/src/modules/signin/worker/use-sign-in.tsx b/packages/apps/human-app/frontend/src/modules/signin/worker/use-sign-in.tsx index 9368785558..477abd65c1 100644 --- a/packages/apps/human-app/frontend/src/modules/signin/worker/use-sign-in.tsx +++ b/packages/apps/human-app/frontend/src/modules/signin/worker/use-sign-in.tsx @@ -7,6 +7,7 @@ export function useSignIn() { const { mutate: signInWorkerMutate, error: signInWorkerError, + isSuccess: isSignInWorkerSuccess, isError: isSignInWorkerError, isPending: isSignInWorkerPending, reset: signInWorkerMutationReset, @@ -23,6 +24,7 @@ export function useSignIn() { return { signIn: handleWorkerSignIn, error: signInWorkerError, + isSuccess: isSignInWorkerSuccess, isError: isSignInWorkerError, isLoading: isSignInWorkerPending, reset: signInWorkerMutationReset, diff --git a/packages/apps/human-app/frontend/src/modules/signin/worker/verification-flow.tsx b/packages/apps/human-app/frontend/src/modules/signin/worker/verification-flow.tsx new file mode 100644 index 0000000000..64a4034535 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/signin/worker/verification-flow.tsx @@ -0,0 +1,128 @@ +import { useCallback, useState } from 'react'; +import { Box, Stack, Typography } from '@mui/material'; +import { t } from 'i18next'; + +import { Chip } from '@/shared/components/ui/chip'; +import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode'; +import { + IdentityVerificationControl, + WalletConnectionControl, +} from '@/modules/worker/profile/components'; +import { KycStatus } from '@/modules/worker/profile/types/profile-types'; +import { useIsMobile } from '@/shared/hooks'; +import { useAuth } from '@/modules/auth/hooks/use-auth'; + +type VerificationStep = 'kyc' | 'wallet'; + +const getChipColor = (status: KycStatus) => { + if (status === KycStatus.APPROVED) { + return 'success.main'; + } else if ( + [KycStatus.RESUBMISSION_REQUESTED, KycStatus.REVIEW].includes(status) + ) { + return 'warning.main'; + } + return 'error.main'; +}; + +export function VerificationFlow({ + isKycApproved, +}: { + isKycApproved: boolean; +}) { + const [step, setStep] = useState( + isKycApproved ? 'wallet' : 'kyc' + ); + + const { user } = useAuth(); + const { colorPalette } = useColorMode(); + const isMobile = useIsMobile(); + + const kycStatus = user?.kyc_status as KycStatus; + + const label = kycStatus + ? t(`worker.profile.idvStatusValues.${kycStatus}`) + : ''; + const isKycDeclined = kycStatus === KycStatus.DECLINED; + const isKycStarted = kycStatus !== KycStatus.NONE; + + const handleKycApproved = useCallback(() => { + setStep('wallet'); + }, []); + + return ( + + + {t('worker.profile.beforeWeGetStarted')} + + + + + + {step === 'kyc' && ( + <> + + {t('worker.profile.identityVerification')} + {isKycStarted && label && ( + + )} + + + + )} + {step === 'wallet' && ( + <> + + {t('worker.profile.connectYourWallet')} + + + + )} + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/signin/worker/verify-user.page.tsx b/packages/apps/human-app/frontend/src/modules/signin/worker/verify-user.page.tsx new file mode 100644 index 0000000000..3b293077d4 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/signin/worker/verify-user.page.tsx @@ -0,0 +1,89 @@ +import { useEffect } from 'react'; +import { Navigate, useNavigate } from 'react-router-dom'; +import { Paper, Stack } from '@mui/material'; + +import { useAuth } from '@/modules/auth/hooks/use-auth'; +import { KycStatus } from '@/modules/worker/profile/types'; +import { routerPaths } from '@/router/router-paths'; +import { useIsUserVerified } from '@/shared/hooks'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { VerificationFlow } from './verification-flow'; +import { Loader } from '@/shared/components/ui/loader'; + +export function VerifyUserPage() { + const navigate = useNavigate(); + const { user, status } = useAuth(); + const { colorPalette } = useColorMode(); + const isUserVerified = useIsUserVerified(); + + const isEmailVerificationPending = + user?.status === 'pending' && !user.kyc_status; + + useEffect(() => { + if (status === 'loading' || !user) { + return; + } + + if (isEmailVerificationPending) { + navigate(routerPaths.verifyEmail, { + replace: true, + state: { routerState: { email: user.email } }, + }); + return; + } + + if (isUserVerified) { + navigate(routerPaths.profile, { replace: true }); + } + }, [status, user, navigate, isUserVerified, isEmailVerificationPending]); + + const isLoading = + status === 'loading' || isEmailVerificationPending || isUserVerified; + + if (!isLoading && !user) { + return ; + } + + return ( + + {isLoading ? ( + + + + ) : ( + + )} + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/components/add-keys/add-keys-form.tsx b/packages/apps/human-app/frontend/src/modules/signup/operator/components/add-keys/add-keys-form.tsx deleted file mode 100644 index 84491fee32..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/components/add-keys/add-keys-form.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Grid } from '@mui/material'; -import { t } from 'i18next'; -import { Link } from 'react-router-dom'; -import { routerPaths } from '@/router/router-paths'; -import { Button } from '@/shared/components/ui/button'; -import { type GetEthKVStoreValuesSuccessResponse } from '@/modules/operator/hooks'; -import { ExistingKeysForm } from './existing-keys-form'; -import { PendingKeysForm } from './pending-keys-form'; - -export function AddKeysForm({ - keysData, -}: Readonly<{ - keysData: GetEthKVStoreValuesSuccessResponse; -}>) { - const hasSomeNotEmptyKeys = Object.values(keysData).some(Boolean); - const hasSomePendingKeys = Object.values(keysData).some((value) => { - /** - * This check is necessary because TS can't infer - * "undefined" from optional object's property - */ - - if (value === undefined) { - return false; - } - - return value.length === 0; - }); - - return ( - -
- {hasSomeNotEmptyKeys ? : null} - {hasSomePendingKeys ? : null} - {hasSomeNotEmptyKeys && !hasSomePendingKeys ? ( - - ) : null} -
-
- ); -} diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/components/add-keys/edit-existing-keys-form.tsx b/packages/apps/human-app/frontend/src/modules/signup/operator/components/add-keys/edit-existing-keys-form.tsx deleted file mode 100644 index 7ef7b929f3..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/components/add-keys/edit-existing-keys-form.tsx +++ /dev/null @@ -1,128 +0,0 @@ -import { Grid, Typography } from '@mui/material'; -import { t } from 'i18next'; -import { useFormState } from 'react-hook-form'; -import type { CustomButtonProps } from '@/shared/components/ui/button'; -import { Button } from '@/shared/components/ui/button'; -import { Input } from '@/shared/components/data-entry/input'; -import type { EthKVStoreKeyValues } from '@/modules/smart-contracts/EthKVStore/config'; -import { - EthKVStoreKeys, - OPERATOR_ROLES, -} from '@/modules/smart-contracts/EthKVStore/config'; -import { Select } from '@/shared/components/data-entry/select'; -import { MultiSelect } from '@/shared/components/data-entry/multi-select'; -import { JOB_TYPES } from '@/shared/consts'; -import type { GetEthKVStoreValuesSuccessResponse } from '@/modules/operator/hooks/use-get-keys'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { PercentsInputMask } from '@/shared/components/data-entry/input-masks'; -import { sortFormKeys, STORE_KEYS_ORDER } from '../../utils'; - -const formInputsConfig: Record = { - [EthKVStoreKeys.Fee]: ( - - ), - [EthKVStoreKeys.PublicKey]: ( - - ), - [EthKVStoreKeys.Url]: ( - - ), - [EthKVStoreKeys.WebhookUrl]: ( - - ), - [EthKVStoreKeys.Role]: ( - - ), - [EthKVStoreKeys.PublicKey]: ( - - ), - [EthKVStoreKeys.Url]: ( - - ), - [EthKVStoreKeys.WebhookUrl]: ( - - ), - [EthKVStoreKeys.Role]: ( - - - - - - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/index.ts deleted file mode 100644 index f4a81ea7f7..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export * from './use-add-stake-mutation-state'; -export * from './use-add-stake'; -export * from './use-edit-existing-keys'; -export * from './use-get-stacked-amount'; -export * from './use-human-token-decimals'; -export * from './use-web3-signup'; diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-add-stake-mutation-state.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-add-stake-mutation-state.ts deleted file mode 100644 index 240b1bfaf9..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-add-stake-mutation-state.ts +++ /dev/null @@ -1,19 +0,0 @@ -import last from 'lodash/last'; -import { useMutationState } from '@tanstack/react-query'; -import type { MutationState } from '@tanstack/react-query'; -import { useConnectedWallet } from '@/shared/contexts/wallet-connect'; -import type { ResponseError } from '@/shared/types/global.type'; -import { type AddStakeCallArguments } from '../schema'; - -export function useAddStakeMutationState() { - const { address } = useConnectedWallet(); - - const state = useMutationState({ - filters: { mutationKey: ['addStake', address] }, - select: (mutation) => mutation.state, - }); - - return last(state) as - | MutationState - | undefined; -} diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-add-stake.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-add-stake.ts deleted file mode 100644 index 0b02feb23f..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-add-stake.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { useNavigate } from 'react-router-dom'; -import { ethers } from 'ethers'; -import { stakingStake } from '@/modules/smart-contracts/Staking/staking-stake'; -import { useConnectedWallet } from '@/shared/contexts/wallet-connect'; -import { getContractAddress } from '@/modules/smart-contracts/get-contract-address'; -import { hmTokenApprove } from '@/modules/smart-contracts/HMToken/hm-token-approve'; -import type { ContractCallArguments } from '@/modules/smart-contracts/types'; -import { routerPaths } from '@/router/router-paths'; -import { hmTokenAllowance } from '@/modules/smart-contracts/HMToken/hm-token-allowance'; -import { type AddStakeCallArguments } from '../schema'; -import { useHMTokenDecimals } from './use-human-token-decimals'; - -async function addStakeMutationFn( - data: AddStakeCallArguments & { - address: string; - amount: string; - decimals?: number; - } & Omit -) { - const stakingContractAddress = getContractAddress({ - contractName: 'Staking', - }); - - const hmTokenContractAddress = getContractAddress({ - contractName: 'HMToken', - }); - - const allowance = await hmTokenAllowance({ - spender: stakingContractAddress, - owner: data.address || '', - contractAddress: hmTokenContractAddress, - provider: data.provider, - signer: data.signer, - chainId: data.chainId, - }); - - const amountBigInt = ethers.parseUnits(data.amount, data.decimals); - if (amountBigInt - allowance > 0) { - await hmTokenApprove({ - spender: stakingContractAddress, - contractAddress: hmTokenContractAddress, - amount: amountBigInt.toString(), - provider: data.provider, - signer: data.signer, - chainId: data.chainId, - }); - } - await stakingStake({ - ...data, - amount: amountBigInt.toString(), - contractAddress: stakingContractAddress, - }); - return data; -} - -export function useAddStake() { - const { - chainId, - address, - web3ProviderMutation: { data: web3data }, - } = useConnectedWallet(); - const { data: HMTDecimals } = useHMTokenDecimals(); - - const navigate = useNavigate(); - - return useMutation({ - mutationFn: (data: AddStakeCallArguments) => - addStakeMutationFn({ - ...data, - address, - provider: web3data?.provider, - signer: web3data?.signer, - chainId, - decimals: HMTDecimals, - }), - onSuccess: () => { - navigate(routerPaths.operator.addKeys); - }, - mutationKey: ['addStake', address], - }); -} diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-edit-existing-keys.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-edit-existing-keys.ts deleted file mode 100644 index 14d3451405..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-edit-existing-keys.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { useMutation, useMutationState } from '@tanstack/react-query'; -import last from 'lodash/last'; -import { useNavigate } from 'react-router-dom'; -import type { JsonRpcSigner } from 'ethers'; -import { routerPaths } from '@/router/router-paths'; -import { useConnectedWallet } from '@/shared/contexts/wallet-connect'; -import { ethKvStoreSetBulk } from '@/modules/smart-contracts/EthKVStore/eth-kv-store-set-bulk'; -import { getContractAddress } from '@/modules/smart-contracts/get-contract-address'; -import { type EditEthKVStoreValuesMutationData } from '../schema'; - -function editExistingKeysMutationFn( - formData: EditEthKVStoreValuesMutationData, - userData: { - accountAddress: string; - chainId: number; - signer?: JsonRpcSigner; - } -) { - const contractAddress = getContractAddress({ - contractName: 'EthKVStore', - }); - - const keys: string[] = []; - const values: string[] = []; - - Object.entries(formData).forEach(([formFieldName, formFieldValue]) => { - if (!formFieldValue) { - return; - } - keys.push(formFieldName); - values.push(formFieldValue.toString()); - }); - - return ethKvStoreSetBulk({ - keys, - values, - contractAddress, - chainId: userData.chainId, - signer: userData.signer, - }); -} - -export function useEditExistingKeysMutation() { - const { - address, - chainId, - web3ProviderMutation: { data: web3Data }, - } = useConnectedWallet(); - - const navigate = useNavigate(); - - return useMutation({ - mutationFn: (data: EditEthKVStoreValuesMutationData) => - editExistingKeysMutationFn(data, { - accountAddress: address, - chainId, - signer: web3Data?.signer, - }), - onSuccess: () => { - navigate(routerPaths.operator.editExistingKeysSuccess); - }, - mutationKey: ['editKeys', address], - }); -} - -export function useEditExistingKeysMutationState() { - const { address } = useConnectedWallet(); - - const state = useMutationState({ - filters: { mutationKey: ['editKeys', address] }, - select: (mutation) => mutation.state, - }); - - return last(state); -} diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-get-stacked-amount.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-get-stacked-amount.ts deleted file mode 100644 index eef5582096..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-get-stacked-amount.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { stakingGetStakedTokens } from '@/modules/smart-contracts/Staking/staking-get-staked-tokens'; -import { useConnectedWallet } from '@/shared/contexts/wallet-connect'; -import { getContractAddress } from '@/modules/smart-contracts/get-contract-address'; - -export function useGetStakedAmount() { - const { - address, - chainId, - web3ProviderMutation: { data }, - } = useConnectedWallet(); - - return useQuery({ - queryFn: async () => { - const contractAddress = getContractAddress({ - contractName: 'Staking', - }); - const stakeAmount = await stakingGetStakedTokens({ - contractAddress, - stakerAddress: address, - chainId, - signer: data?.signer, - }); - - return stakeAmount; - }, - queryKey: ['getStackedAmount', address, chainId, data?.signer], - refetchInterval: 0, - }); -} diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-human-token-decimals.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-human-token-decimals.ts deleted file mode 100644 index 07a3e00a2c..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-human-token-decimals.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { useConnectedWallet } from '@/shared/contexts/wallet-connect'; -import { hmTokenDecimals } from '@/modules/smart-contracts/HMToken/hm-token-decimals'; -import { getContractAddress } from '@/modules/smart-contracts/get-contract-address'; - -export function useHMTokenDecimals() { - const { - chainId, - web3ProviderMutation: { data }, - } = useConnectedWallet(); - - return useQuery({ - queryFn: () => { - const contractAddress = getContractAddress({ - contractName: 'HMToken', - }); - return hmTokenDecimals({ - contractAddress, - chainId, - signer: data?.signer, - }); - }, - queryKey: ['decimals', chainId, data?.signer ?? 'signer'], - }); -} diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-web3-signup.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-web3-signup.ts deleted file mode 100644 index d0592b6479..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/hooks/use-web3-signup.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; -import { useNavigate } from 'react-router-dom'; -import { useConnectedWallet } from '@/shared/contexts/wallet-connect'; -import { useWeb3Auth } from '@/modules/auth-web3/hooks/use-web3-auth'; -import { routerPaths } from '@/router/router-paths'; -import * as signupService from '@/modules/signup/services/signup.service'; - -export function useWeb3SignUp() { - const { address, chainId } = useConnectedWallet(); - const { signIn } = useWeb3Auth(); - const navigate = useNavigate(); - return useMutation({ - mutationFn: async ({ signature }: { signature: string }) => - signupService.operatorWeb3SignUp({ - signature, - address, - }), - onSuccess: (successResponse) => { - signIn(successResponse); - navigate(routerPaths.operator.profile); - }, - mutationKey: ['web3SignUp', address, chainId], - }); -} diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/index.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/index.ts deleted file mode 100644 index 7929623807..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './views'; diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/schema/add-stake-amount-call-arguments-schema.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/schema/add-stake-amount-call-arguments-schema.ts deleted file mode 100644 index 0160ee1c00..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/schema/add-stake-amount-call-arguments-schema.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { z } from 'zod'; -import { t } from 'i18next'; - -type AmountValidation = z.ZodType; - -type AmountField = z.infer; - -export const addStakeAmountCallArgumentsSchema = ( - decimals: number -): AmountValidation => - z - .string() - .refine((amount) => !amount.startsWith('-')) - .refine( - (amount) => { - const decimalPart = amount.toString().split('.')[1]; - if (!decimalPart) return true; - return decimalPart.length <= decimals; - }, - { - message: t('operator.stakeForm.invalidDecimals', { - decimals, - }), - } - ); - -export interface AddStakeCallArguments { - amount: AmountField; -} diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/schema/eth-kv-store-values-mutation-schema.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/schema/eth-kv-store-values-mutation-schema.ts deleted file mode 100644 index 68d88391c6..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/schema/eth-kv-store-values-mutation-schema.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { z, ZodError } from 'zod'; -import { t } from 'i18next'; -import { - EthKVStoreKeys, - JobType, - OPERATOR_ROLES, -} from '@/modules/smart-contracts/EthKVStore/config'; -import { type GetEthKVStoreValuesSuccessResponse } from '@/modules/operator/hooks/use-get-keys'; -import { urlDomainSchema } from '@/shared/schemas'; - -const fieldsValidations = { - [EthKVStoreKeys.PublicKey]: urlDomainSchema, - [EthKVStoreKeys.Url]: urlDomainSchema, - [EthKVStoreKeys.WebhookUrl]: urlDomainSchema, - [EthKVStoreKeys.Role]: z.enum(OPERATOR_ROLES), - [EthKVStoreKeys.JobTypes]: z.array(z.enum(JobType)).min(1), - [EthKVStoreKeys.Fee]: z.coerce - .number({ - error: (issue) => - issue.input === undefined - ? t('validation.required') - : t('validation.notANumber'), - }) - .min(0, t('validation.feeValidationError')) - .max(100, t('validation.feeValidationError')) - .multipleOf(1, t('validation.feeValidationError')), -}; - -export const editEthKVStoreValuesMutationSchema = z.object({ - [EthKVStoreKeys.PublicKey]: - fieldsValidations[EthKVStoreKeys.PublicKey].optional(), - [EthKVStoreKeys.Url]: fieldsValidations[EthKVStoreKeys.Url].optional(), - [EthKVStoreKeys.WebhookUrl]: - fieldsValidations[EthKVStoreKeys.WebhookUrl].optional(), - [EthKVStoreKeys.Role]: fieldsValidations[EthKVStoreKeys.Role].optional(), - [EthKVStoreKeys.JobTypes]: - fieldsValidations[EthKVStoreKeys.JobTypes].optional(), - [EthKVStoreKeys.Fee]: fieldsValidations[EthKVStoreKeys.Fee].optional(), -}); - -export type EditEthKVStoreValuesMutationData = z.infer< - typeof editEthKVStoreValuesMutationSchema ->; - -export const setEthKVStoreValuesMutationSchema = ( - initialFields: GetEthKVStoreValuesSuccessResponse -) => { - return editEthKVStoreValuesMutationSchema.superRefine((newData, ctx) => { - Object.keys(initialFields).forEach((key) => { - const typedKey = key as keyof GetEthKVStoreValuesSuccessResponse; - const initialField = initialFields[typedKey]; - if (!initialField) { - try { - fieldsValidations[typedKey].parse(newData[typedKey]); - } catch (error) { - if (error instanceof ZodError) { - const issue = error.issues[0]; - ctx.addIssue({ - ...issue, - path: [typedKey], - }); - } - } - } - }); - }); -}; - -export const getEditEthKVStoreValuesMutationSchema = ( - initialData: GetEthKVStoreValuesSuccessResponse -) => { - return editEthKVStoreValuesMutationSchema.transform( - (newData, ctx) => { - const fieldsThatHasChanges: EditEthKVStoreValuesMutationData = {}; - Object.values(EthKVStoreKeys).forEach((key) => { - const newFiledData = newData[key]; - const initialFiledData = initialData[key]; - - let hasFieldChanged = false; - if (Array.isArray(newFiledData) && Array.isArray(initialFiledData)) { - if ( - newFiledData.sort().toString() !== - initialFiledData.sort().toString() - ) { - hasFieldChanged = true; - } - } else if ( - typeof newFiledData === 'number' && - newFiledData.toString() !== initialFiledData?.toString() - ) { - hasFieldChanged = true; - } else { - hasFieldChanged = newFiledData != initialFiledData; - } - - if (hasFieldChanged) { - Object.assign(fieldsThatHasChanges, { [key]: newFiledData }); - } - }); - - if (Object.values(fieldsThatHasChanges).length === 0) { - ctx.addIssue({ - code: z.ZodIssueCode.custom, - message: t('operator.addKeysPage.editKeysForm.error'), - path: ['form'], - }); - - return z.NEVER; - } - - return fieldsThatHasChanges; - } - ) as z.ZodType< - EditEthKVStoreValuesMutationData, - GetEthKVStoreValuesSuccessResponse - >; -}; diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/schema/index.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/schema/index.ts deleted file mode 100644 index 0eeb42e505..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/schema/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './add-stake-amount-call-arguments-schema'; -export * from './eth-kv-store-values-mutation-schema'; diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/utils/__tests__/staked-amount-formatter.test.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/utils/__tests__/staked-amount-formatter.test.ts deleted file mode 100644 index fcbd9da66d..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/utils/__tests__/staked-amount-formatter.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { stakedAmountFormatter } from '../staked-amount-formatter'; -import '@/shared/i18n/i18n'; - -describe('stakedAmountFormatter Function', () => { - it('should format amounts with no decimal part correctly', () => { - const amount = BigInt('1000000000000000000'); - - const result = stakedAmountFormatter(amount); - expect(result).toBe('1 HMT'); - }); - - it('should format amounts with decimal part correctly', () => { - const amount = BigInt('1500000000000000000'); - - const result = stakedAmountFormatter(amount); - expect(result).toBe('1.5 HMT'); - }); - - it('should handle very small amounts', () => { - const amount = BigInt('1'); - - const result = stakedAmountFormatter(amount); - expect(result).toBe('0.000000000000000001 HMT'); - }); - - it('should handle zero amount', () => { - const amount = BigInt('0'); - - const result = stakedAmountFormatter(amount); - expect(result).toBe('0 HMT'); - }); - - it('should handle large amounts', () => { - const amount = BigInt('1000000000000000000000'); - - const result = stakedAmountFormatter(amount); - expect(result).toBe('1000 HMT'); - }); -}); diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/utils/index.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/utils/index.ts deleted file mode 100644 index 8c9632fa1b..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/utils/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './sort-form'; -export * from './staked-amount-formatter'; diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/utils/sort-form.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/utils/sort-form.ts deleted file mode 100644 index 57693c0a40..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/utils/sort-form.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { EthKVStoreKeyValues } from '@/modules/smart-contracts/EthKVStore/config'; -import { EthKVStoreKeys } from '@/modules/smart-contracts/EthKVStore/config'; - -export const sortFormKeys = ( - keys: EthKVStoreKeyValues[], - order: EthKVStoreKeyValues[] -): EthKVStoreKeyValues[] => { - return keys.sort((a, b) => order.indexOf(a) - order.indexOf(b)); -}; - -export const STORE_KEYS_ORDER: EthKVStoreKeyValues[] = [ - EthKVStoreKeys.Fee, - EthKVStoreKeys.PublicKey, - EthKVStoreKeys.Url, - EthKVStoreKeys.WebhookUrl, - EthKVStoreKeys.Role, - EthKVStoreKeys.JobTypes, -]; diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/utils/staked-amount-formatter.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/utils/staked-amount-formatter.ts deleted file mode 100644 index c63e113faf..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/utils/staked-amount-formatter.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ethers } from 'ethers'; -import { t } from 'i18next'; - -export const stakedAmountFormatter = (amount: bigint) => { - const amountAsString = ethers.formatEther(amount); - - if (amountAsString.split('.')[1] === '0') { - // decimals part should be omitted - return `${amountAsString.replace('.0', '')} ${t('inputMasks.humanCurrencySuffix')}`; - } - return `${ethers.formatEther(amount)} ${t('inputMasks.humanCurrencySuffix')}`; -}; diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/views/add-keys.page.tsx b/packages/apps/human-app/frontend/src/modules/signup/operator/views/add-keys.page.tsx deleted file mode 100644 index bee5b1bfe1..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/views/add-keys.page.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { t } from 'i18next'; -import { - PageCardError, - PageCardLoader, - PageCard, -} from '@/shared/components/ui/page-card'; -import { getErrorMessageForError, jsonRpcErrorHandler } from '@/shared/errors'; -import { Alert } from '@/shared/components/ui/alert'; -import { useGetKeys } from '@/modules/operator/hooks'; -import { useEditExistingKeysMutationState } from '../hooks'; -import { AddKeysForm } from '../components/add-keys'; - -export function AddKeysOperatorPage() { - const { - data: keysData, - isError: isGetKeysError, - error: getKeysError, - isPending: isGetKeysPending, - } = useGetKeys(); - const editExistingKeysMutationState = useEditExistingKeysMutationState(); - - const errorAlert = editExistingKeysMutationState?.error ? ( - - {getErrorMessageForError( - editExistingKeysMutationState.error, - jsonRpcErrorHandler - )} - - ) : undefined; - - if (isGetKeysError) { - return ( - - ); - } - - if (isGetKeysPending) { - return ; - } - - return ( - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/views/add-stake.page.tsx b/packages/apps/human-app/frontend/src/modules/signup/operator/views/add-stake.page.tsx deleted file mode 100644 index 0a32dc50f7..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/views/add-stake.page.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import Grid from '@mui/material/Grid'; -import { Typography } from '@mui/material'; -import { useState } from 'react'; -import { t } from 'i18next'; -import { - PageCardError, - PageCardLoader, - PageCard, -} from '@/shared/components/ui/page-card'; -import { getErrorMessageForError } from '@/shared/errors'; -import { Alert } from '@/shared/components/ui/alert'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette'; -import { - useAddStakeMutationState, - useGetStakedAmount, - useHMTokenDecimals, -} from '../hooks'; -import { StakeForm, Buttons } from '../components/add-stake'; -import { stakedAmountFormatter } from '../utils'; - -export function AddStakeOperatorPage() { - const { colorPalette, isDarkMode } = useColorMode(); - const [displayForm, setDisplayForm] = useState(false); - const { - data: stakedAmount, - isError: isGetStakedAmountError, - error: getStackedAmountError, - isPending: isGetStakedAmountPending, - } = useGetStakedAmount(); - - const addStakeMutationState = useAddStakeMutationState(); - - const { - data: decimalsData, - error: decimalsDataError, - isPending: isDecimalsDataPending, - } = useHMTokenDecimals(); - - const getAlert = () => { - if (!addStakeMutationState) return undefined; - - switch (addStakeMutationState.status) { - case 'error': - return ( - - {getErrorMessageForError(addStakeMutationState.error)} - - ); - case 'success': - return ( - - {t('operator.stakeForm.successAlert', { - amount: addStakeMutationState.variables?.amount - ? addStakeMutationState.variables.amount - : -1, - })} - - ); - - default: - return undefined; - } - }; - - if (isGetStakedAmountError || decimalsDataError) { - return ( - - ); - } - - if (isGetStakedAmountPending || isDecimalsDataPending) { - return ; - } - - return ( - - - - {t('operator.addStake.formHeader')} - - - {t('operator.addStake.label')} - - - {stakedAmountFormatter(stakedAmount)} - - {displayForm ? ( - - ) : ( - - )} - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/views/connect-wallet.page.tsx b/packages/apps/human-app/frontend/src/modules/signup/operator/views/connect-wallet.page.tsx deleted file mode 100644 index 53a5e85ec3..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/views/connect-wallet.page.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import { Grid, Typography } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { Navigate } from 'react-router-dom'; -import { PageCard } from '@/shared/components/ui/page-card'; -import { useWalletConnect } from '@/shared/contexts/wallet-connect'; -import { Alert } from '@/shared/components/ui/alert'; -import { getErrorMessageForError } from '@/shared/errors'; -import { Button } from '@/shared/components/ui/button'; -import { routerPaths } from '@/router/router-paths'; -import { useWalletConnectModal } from '@/modules/auth-web3/hooks/use-wallet-connect-modal'; - -export function ConnectWalletOperatorPage() { - const { t } = useTranslation(); - const { - isConnected, - web3ProviderMutation: { - error: web3ProviderError, - status: web3ProviderStatus, - }, - } = useWalletConnect(); - const { openModal } = useWalletConnectModal(); - - const getAlert = () => { - if (web3ProviderStatus === 'error') - return ( - - {getErrorMessageForError(web3ProviderError)} - - ); - - if (isConnected) - return ( - - {t('operator.connectWallet.successAlert')} - - ); - - return undefined; - }; - - if (isConnected) { - return ; - } - - return ( - - - - {t('operator.connectWallet.description')} - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/views/edit-existing-keys-success.page.tsx b/packages/apps/human-app/frontend/src/modules/signup/operator/views/edit-existing-keys-success.page.tsx deleted file mode 100644 index f7560bfcef..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/views/edit-existing-keys-success.page.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { t } from 'i18next'; -import { Grid, Typography } from '@mui/material'; -import { - PageCardError, - PageCardLoader, - PageCard, -} from '@/shared/components/ui/page-card'; -import { Button } from '@/shared/components/ui/button'; -import { getErrorMessageForError } from '@/shared/errors'; -import { Alert } from '@/shared/components/ui/alert'; -import { useConnectedWallet } from '@/shared/contexts/wallet-connect'; -import { usePrepareSignature } from '@/shared/hooks'; -import { - PrepareSignatureType, - type SignatureData, -} from '@/shared/services/signature.service'; -import { useWeb3SignUp } from '../hooks'; - -export function EditExistingKeysSuccessPage() { - const { signMessage } = useConnectedWallet(); - const { - prepareSignature, - isError: isSignatureDataError, - error: errorSignatureDataError, - isPending: isSignatureDataPending, - } = usePrepareSignature(PrepareSignatureType.SIGN_UP); - - const { - mutate: web3SignUpMutation, - isError: isWeb3SignUpError, - error: web3SignUpError, - isPending: web3SignUpPending, - } = useWeb3SignUp(); - - const handleWeb3SignUp = async () => { - const data: SignatureData = await prepareSignature(); - const signature = await signMessage(JSON.stringify(data)); - web3SignUpMutation({ signature: signature ?? '' }); - }; - - if (isSignatureDataError) { - return ( - - ); - } - - if (isSignatureDataPending) { - return ; - } - - return ( - - {getErrorMessageForError(web3SignUpError)} - - ) : undefined - } - showBackButton={false} - showCancelButton={false} - title={t('operator.editExistingKeysSuccess.title')} - > - - - - {t('operator.editExistingKeysSuccess.paragraph1')} - - - {t('operator.editExistingKeysSuccess.paragraph2')} - - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/views/index.ts b/packages/apps/human-app/frontend/src/modules/signup/operator/views/index.ts deleted file mode 100644 index 0205a5c729..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/views/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './add-keys.page'; -export * from './add-stake.page'; -export * from './connect-wallet.page'; -export * from './set-up-operator.page'; -export * from './edit-existing-keys-success.page'; diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/views/set-up-operator.page.tsx b/packages/apps/human-app/frontend/src/modules/signup/operator/views/set-up-operator.page.tsx deleted file mode 100644 index 5c0386bf4e..0000000000 --- a/packages/apps/human-app/frontend/src/modules/signup/operator/views/set-up-operator.page.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Grid, Typography } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { Link } from 'react-router-dom'; -import { PageCard } from '@/shared/components/ui/page-card'; -import { Alert } from '@/shared/components/ui/alert'; -import { Button } from '@/shared/components/ui/button'; -import { routerPaths } from '@/router/router-paths'; -import { useConnectedWallet } from '@/shared/contexts/wallet-connect'; - -export function SetUpOperatorPage() { - const { t } = useTranslation(); - useConnectedWallet(); - return ( - - {t('operator.connectWallet.successAlert')} - - } - title={t('operator.connectWallet.title')} - > - - - {t('operator.connectWallet.description')} - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/signup/services/signup.service.ts b/packages/apps/human-app/frontend/src/modules/signup/services/signup.service.ts index 7eb9628b22..f410015543 100644 --- a/packages/apps/human-app/frontend/src/modules/signup/services/signup.service.ts +++ b/packages/apps/human-app/frontend/src/modules/signup/services/signup.service.ts @@ -1,31 +1,11 @@ -import { z } from 'zod'; import { ApiClientError, humanAppApiClient } from '@/api'; import { type SignUpDto } from '../worker/schema'; -const apiPaths = { - worker: { - signUp: '/auth/signup', - }, - operator: { - web3Auth: { - signUp: '/auth/web3/signup', - }, - }, -}; +const signUpPath = '/auth/signup'; -// eslint-disable-next-line @typescript-eslint/no-unused-vars -const web3SignInSuccessResponseSchema = z.object({ - access_token: z.string(), - refresh_token: z.string(), -}); - -type Web3SignInSuccessResponse = z.infer< - typeof web3SignInSuccessResponseSchema ->; - -async function workerSignUp(data: Omit) { +async function signUp(data: Omit) { try { - const result = await humanAppApiClient.post(apiPaths.worker.signUp, { + const result = await humanAppApiClient.post(signUpPath, { body: { email: data.email, password: data.password, @@ -42,24 +22,4 @@ async function workerSignUp(data: Omit) { } } -async function operatorWeb3SignUp(data: { - signature: string; - address: string; -}) { - try { - const result = await humanAppApiClient.post( - apiPaths.operator.web3Auth.signUp, - { - body: data, - } - ); - return result; - } catch (error) { - if (error instanceof ApiClientError) { - throw error; - } - throw new Error('Failed to sign up operator with web3'); - } -} - -export { workerSignUp, operatorWeb3SignUp }; +export { signUp }; diff --git a/packages/apps/human-app/frontend/src/modules/signup/worker/hooks/use-sign-up-mutation.ts b/packages/apps/human-app/frontend/src/modules/signup/worker/hooks/use-sign-up-mutation.ts index e4b28846d3..c830050056 100644 --- a/packages/apps/human-app/frontend/src/modules/signup/worker/hooks/use-sign-up-mutation.ts +++ b/packages/apps/human-app/frontend/src/modules/signup/worker/hooks/use-sign-up-mutation.ts @@ -1,5 +1,6 @@ import { useMutation } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; + import { routerPaths } from '@/router/router-paths'; import * as signupService from '@/modules/signup/services/signup.service'; import { type SignUpDto } from '../schema'; @@ -9,10 +10,10 @@ export function useSignUpMutation() { return useMutation({ mutationFn: async (data: Omit) => { - return signupService.workerSignUp(data); + return signupService.signUp(data); }, onSuccess: (_, { email }) => { - navigate(routerPaths.worker.verifyEmail, { + navigate(routerPaths.verifyEmail, { state: { routerState: { email } }, }); }, diff --git a/packages/apps/human-app/frontend/src/modules/signup/worker/hooks/use-sign-up-worker.tsx b/packages/apps/human-app/frontend/src/modules/signup/worker/hooks/use-sign-up-worker.tsx index bef557d9d1..e632f3f7e0 100644 --- a/packages/apps/human-app/frontend/src/modules/signup/worker/hooks/use-sign-up-worker.tsx +++ b/packages/apps/human-app/frontend/src/modules/signup/worker/hooks/use-sign-up-worker.tsx @@ -1,5 +1,6 @@ import { useEffect } from 'react'; import omit from 'lodash/omit'; + import { browserAuthProvider } from '@/shared/contexts/browser-auth-provider'; import { type SignUpDto } from '../schema'; import { useSignUpMutation } from './use-sign-up-mutation'; diff --git a/packages/apps/human-app/frontend/src/modules/signup/worker/views/sign-up-worker.page.tsx b/packages/apps/human-app/frontend/src/modules/signup/worker/views/sign-up-worker.page.tsx index c2f8f63f0b..bb8dd28edd 100644 --- a/packages/apps/human-app/frontend/src/modules/signup/worker/views/sign-up-worker.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/signup/worker/views/sign-up-worker.page.tsx @@ -1,24 +1,40 @@ +import { useEffect } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; +import { Link as RouterLink, useNavigate } from 'react-router-dom'; import { zodResolver } from '@hookform/resolvers/zod'; -import Grid from '@mui/material/Grid'; -import Typography from '@mui/material/Typography'; -import Link from '@mui/material/Link'; -import { useTranslation, Trans } from 'react-i18next'; +import { Box, Divider, Grid, Link, Paper, Typography } from '@mui/material'; +import { Trans } from 'react-i18next'; +import { t } from 'i18next'; import { Button } from '@/shared/components/ui/button'; import { Input } from '@/shared/components/data-entry/input'; -import { Password } from '@/shared/components/data-entry/password/password'; -import { PageCard } from '@/shared/components/ui/page-card'; +import { Password } from '@/shared/components/data-entry/password'; import { env } from '@/shared/env'; import { getErrorMessageForError } from '@/shared/errors'; -import { Alert } from '@/shared/components/ui/alert'; import { HCaptchaForm } from '@/shared/components/hcaptcha'; import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; import { useSignUpWorker } from '@/modules/signup/worker/hooks/use-sign-up-worker'; import { ApiClientError } from '@/api'; import { signUpDtoSchema } from '../schema'; +import signUpImage from '@/assets/background-images/signup-background.png'; +import { BackButton } from '@/shared/components/ui/page-card/back-button'; -export function SignUpWorkerPage() { - const { t } = useTranslation(); +import { routerPaths } from '@/router/router-paths'; +import { + TopNotificationType, + useNotification, +} from '@/shared/hooks/use-notification'; +import { useColorMode } from '@/shared/contexts/color-mode'; + +function handleSignupError(unknownError: unknown) { + if (unknownError instanceof ApiClientError && unknownError.status === 409) { + return t('worker.signUpForm.errors.emailTaken'); + } +} + +export function SignUpPage() { + const { showNotification } = useNotification(); + const navigate = useNavigate(); + const { colorPalette } = useColorMode(); const { signUp, error, isError, isLoading, reset } = useSignUpWorker(); const methods = useForm({ defaultValues: { @@ -30,12 +46,20 @@ export function SignUpWorkerPage() { resolver: zodResolver(signUpDtoSchema), }); + useEffect(() => { + if (isError) { + const errorMessage = getErrorMessageForError(error, handleSignupError); + showNotification({ + message: errorMessage, + type: TopNotificationType.ERROR, + }); + } + }, [isError, error, showNotification]); + useResetMutationErrors(methods.watch, reset); - const handleSignupError = (unknownError: unknown) => { - if (unknownError instanceof ApiClientError && unknownError.status === 409) { - return t('worker.signUpForm.errors.emailTaken'); - } + const handleBackButton = () => { + navigate(-1); }; const handleSubmit = (event: React.FormEvent) => { @@ -43,63 +67,190 @@ export function SignUpWorkerPage() { }; return ( - - {getErrorMessageForError(error, handleSignupError)} - - ) : undefined - } - title={t('worker.signUpForm.title')} + - -
- - - - - - - - ), - 2: ( - - ), - }} - i18nKey="worker.signUpForm.termsOfServiceAndPrivacyPolicy" - /> - - - - - -
-
-
+ {t('worker.signUpForm.title')} + +
+ +
+ + + + + + + ), + 2: ( + + ), + }} + i18nKey="worker.signUpForm.termsOfServiceAndPrivacyPolicy" + /> + + + + + + + {t('worker.signUpForm.or')} + + + *': { + display: 'inline-flex', + }, + }} + > + + {t('worker.signUpForm.alreadyHaveAccount')} + {' '} + + {t('worker.signUpForm.signIn')} + + + +
+
+ + + + + + ); } diff --git a/packages/apps/human-app/frontend/src/modules/smart-contracts/EthKVStore/config.ts b/packages/apps/human-app/frontend/src/modules/smart-contracts/EthKVStore/config.ts deleted file mode 100644 index 60e089c9c2..0000000000 --- a/packages/apps/human-app/frontend/src/modules/smart-contracts/EthKVStore/config.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { Role } from '@human-protocol/sdk/src/constants'; - -export const OPERATOR_ROLES = [ - Role.ExchangeOracle, - Role.JobLauncher, - Role.RecordingOracle, -] as const; - -export enum JobType { - FORTUNE = 'fortune', - POINTS = 'image_points', - BOUNDING_BOXES = 'image_boxes', - BOUNDING_BOXES_FROM_POINTS = 'image_boxes_from_points', - SKELETONS_FROM_BOUNDING_BOXES = 'image_skeletons_from_boxes', - POLYGONS = 'image_polygons', - AUDIO_TRANSCRIPTION = 'audio_transcription', - AUDIO_ATTRIBUTE_ANNOTATION = 'audio_attribute_annotation', - SOCIAL_MEDIA_PROMOTION = 'social_media_promotion', - SOCIAL_MEDIA_ENGAGEMENT = 'social_media_engagement', -} - -export const EthKVStoreKeys = { - PublicKey: 'public_key', - Url: 'url', - WebhookUrl: 'webhook_url', - Role: 'role', - Fee: 'fee', - JobTypes: 'job_types', -} as const; - -export type EthKVStoreKeyValues = - (typeof EthKVStoreKeys)[keyof typeof EthKVStoreKeys]; - -export type SetBulkKeys = string[]; - -export type SetBulkValues = string[]; -export interface SetOperatorPayload { - keys: SetBulkKeys; - values: SetBulkValues; -} - -export type KYCKey = `KYC-${string}`; -export interface SetKYCPayload { - keys: [KYCKey]; - values: [string]; -} diff --git a/packages/apps/human-app/frontend/src/modules/smart-contracts/EthKVStore/eth-kv-store-get-keys.ts b/packages/apps/human-app/frontend/src/modules/smart-contracts/EthKVStore/eth-kv-store-get-keys.ts deleted file mode 100644 index 97369c0722..0000000000 --- a/packages/apps/human-app/frontend/src/modules/smart-contracts/EthKVStore/eth-kv-store-get-keys.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { Contract } from 'ethers'; -import EthKVStore from '@/modules/smart-contracts/abi/EthKVStore.json'; -import type { ContractCallArguments } from '@/modules/smart-contracts/types'; -import { EthKVStoreKeys } from '@/modules/smart-contracts/EthKVStore/config'; -import { JsonRpcError } from '@/modules/smart-contracts/json-rpc-error'; - -export async function ethKVStoreGetKeys({ - accountAddress, - contractAddress, - signer, -}: { accountAddress: string } & ContractCallArguments) { - try { - const ethKVStoreContract = new Contract( - contractAddress, - EthKVStore.abi, - signer - ); - const keys = (await Promise.all([ - ethKVStoreContract.get(accountAddress, EthKVStoreKeys.PublicKey), - ethKVStoreContract.get(accountAddress, EthKVStoreKeys.Url), - ethKVStoreContract.get(accountAddress, EthKVStoreKeys.WebhookUrl), - ethKVStoreContract.get(accountAddress, EthKVStoreKeys.Role), - ethKVStoreContract.get(accountAddress, EthKVStoreKeys.JobTypes), - ethKVStoreContract.get(accountAddress, EthKVStoreKeys.Fee), - ])) as unknown as string[]; - - return { - [EthKVStoreKeys.PublicKey]: keys[0], - [EthKVStoreKeys.Url]: keys[1], - [EthKVStoreKeys.WebhookUrl]: keys[2], - [EthKVStoreKeys.Role]: keys[3], - [EthKVStoreKeys.JobTypes]: keys[4], - [EthKVStoreKeys.Fee]: keys[5], - }; - } catch (error) { - throw new JsonRpcError(error); - } -} diff --git a/packages/apps/human-app/frontend/src/modules/smart-contracts/EthKVStore/eth-kv-store-get-kyc-data.ts b/packages/apps/human-app/frontend/src/modules/smart-contracts/EthKVStore/eth-kv-store-get-kyc-data.ts deleted file mode 100644 index b1c1534671..0000000000 --- a/packages/apps/human-app/frontend/src/modules/smart-contracts/EthKVStore/eth-kv-store-get-kyc-data.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { Contract, ethers } from 'ethers'; -import EthKVStore from '@/modules/smart-contracts/abi/EthKVStore.json'; -import type { ContractCallArguments } from '@/modules/smart-contracts/types'; -import type { KYCKey } from '@/modules/smart-contracts/EthKVStore/config'; -import { JsonRpcError } from '@/modules/smart-contracts/json-rpc-error'; -import { env } from '@/shared/env'; -import type { ChainWithAddresses } from '@/modules/smart-contracts/chains'; -import { MainnetChains, TestnetChains } from '@/modules/smart-contracts/chains'; - -export async function ethKVStoreGetKycData({ - kycKey, - accountAddress, - contractAddress, -}: { kycKey: KYCKey; accountAddress: string } & Omit< - ContractCallArguments, - 'chainId' | 'signer' ->) { - try { - let chain: ChainWithAddresses | undefined; - - if (env.VITE_NETWORK === 'mainnet') { - chain = MainnetChains[0]; - } else { - chain = TestnetChains[0]; - } - - const provider = new ethers.JsonRpcProvider(chain.rpcUrl); - - const ethKVStoreContract = new Contract( - contractAddress, - EthKVStore.abi, - provider - ); - - const result = (await ethKVStoreContract.get(accountAddress, kycKey)) as - | string - | null; - - return result; - } catch (error) { - throw new JsonRpcError(error); - } -} diff --git a/packages/apps/human-app/frontend/src/modules/smart-contracts/EthKVStore/eth-kv-store-set-bulk.ts b/packages/apps/human-app/frontend/src/modules/smart-contracts/EthKVStore/eth-kv-store-set-bulk.ts deleted file mode 100644 index 45c1227d35..0000000000 --- a/packages/apps/human-app/frontend/src/modules/smart-contracts/EthKVStore/eth-kv-store-set-bulk.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { Contract } from 'ethers'; -import EthKVStore from '@/modules/smart-contracts/abi/EthKVStore.json'; -import type { ContractCallArguments } from '@/modules/smart-contracts/types'; -import type { - SetKYCPayload, - SetOperatorPayload, -} from '@/modules/smart-contracts/EthKVStore/config'; -import { JsonRpcError } from '@/modules/smart-contracts/json-rpc-error'; - -export async function ethKvStoreSetBulk({ - keys, - values, - contractAddress, - signer, -}: (SetOperatorPayload | SetKYCPayload) & ContractCallArguments) { - try { - const ethKVStoreContract = new Contract( - contractAddress, - EthKVStore.abi, - signer - ); - - const tx = await ethKVStoreContract.setBulk(keys, values); - await tx.wait(); - } catch (error) { - throw new JsonRpcError(error); - } -} diff --git a/packages/apps/human-app/frontend/src/modules/smart-contracts/HMToken/hm-token-allowance.ts b/packages/apps/human-app/frontend/src/modules/smart-contracts/HMToken/hm-token-allowance.ts deleted file mode 100644 index 5d45bdf21a..0000000000 --- a/packages/apps/human-app/frontend/src/modules/smart-contracts/HMToken/hm-token-allowance.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Contract } from 'ethers'; -import HMToken from '@/modules/smart-contracts/abi/HMToken.json'; -import type { ContractCallArguments } from '@/modules/smart-contracts/types'; -import { JsonRpcError } from '@/modules/smart-contracts/json-rpc-error'; - -export async function hmTokenAllowance({ - spender, - owner, - contractAddress, - signer, -}: { spender: string; owner: string } & ContractCallArguments) { - try { - const hmTokenContract = new Contract(contractAddress, HMToken.abi, signer); - const allowanceResult = (await hmTokenContract.getFunction('allowance')( - owner, - spender - )) as Promise; - - return allowanceResult; - } catch (error) { - throw new JsonRpcError(error); - } -} diff --git a/packages/apps/human-app/frontend/src/modules/smart-contracts/HMToken/hm-token-approve.ts b/packages/apps/human-app/frontend/src/modules/smart-contracts/HMToken/hm-token-approve.ts deleted file mode 100644 index b72302e4d1..0000000000 --- a/packages/apps/human-app/frontend/src/modules/smart-contracts/HMToken/hm-token-approve.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Contract } from 'ethers'; -import HMToken from '@/modules/smart-contracts/abi/HMToken.json'; -import type { ContractCallArguments } from '@/modules/smart-contracts/types'; -import { JsonRpcError } from '@/modules/smart-contracts/json-rpc-error'; - -export async function hmTokenApprove({ - contractAddress, - spender, - amount, - signer, -}: { - spender: string; - amount: string; -} & ContractCallArguments) { - try { - const hmTokenContract = new Contract(contractAddress, HMToken.abi, signer); - const tx = await hmTokenContract.approve(spender, amount); - await tx.wait(); - } catch (error) { - throw new JsonRpcError(error); - } -} diff --git a/packages/apps/human-app/frontend/src/modules/smart-contracts/HMToken/hm-token-decimals.ts b/packages/apps/human-app/frontend/src/modules/smart-contracts/HMToken/hm-token-decimals.ts deleted file mode 100644 index be04a3fd01..0000000000 --- a/packages/apps/human-app/frontend/src/modules/smart-contracts/HMToken/hm-token-decimals.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Contract } from 'ethers'; -import HMToken from '@/modules/smart-contracts/abi/HMToken.json'; -import type { ContractCallArguments } from '@/modules/smart-contracts/types'; -import { JsonRpcError } from '@/modules/smart-contracts/json-rpc-error'; - -export async function hmTokenDecimals({ - contractAddress, - signer, -}: ContractCallArguments) { - try { - const hmTokenContract = new Contract(contractAddress, HMToken.abi, signer); - const decimalsResult = (await hmTokenContract.getFunction( - 'decimals' - )()) as Promise; - - return Number(decimalsResult); - } catch (error) { - throw new JsonRpcError(error); - } -} diff --git a/packages/apps/human-app/frontend/src/modules/smart-contracts/Staking/staking-get-staked-tokens.ts b/packages/apps/human-app/frontend/src/modules/smart-contracts/Staking/staking-get-staked-tokens.ts deleted file mode 100644 index 3be323970e..0000000000 --- a/packages/apps/human-app/frontend/src/modules/smart-contracts/Staking/staking-get-staked-tokens.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Contract } from 'ethers'; -import Staking from '@/modules/smart-contracts/abi/Staking.json'; -import type { ContractCallArguments } from '@/modules/smart-contracts/types'; -import { JsonRpcError } from '@/modules/smart-contracts/json-rpc-error'; - -export async function stakingGetStakedTokens({ - contractAddress, - stakerAddress, - signer, -}: { - stakerAddress: string; -} & ContractCallArguments) { - try { - const stakingContract = new Contract(contractAddress, Staking.abi, signer); - return (await stakingContract.getFunction('getStakedTokens')( - stakerAddress - )) as Promise; - } catch (error) { - throw new JsonRpcError(error); - } -} diff --git a/packages/apps/human-app/frontend/src/modules/smart-contracts/Staking/staking-stake.ts b/packages/apps/human-app/frontend/src/modules/smart-contracts/Staking/staking-stake.ts deleted file mode 100644 index 0756aa296c..0000000000 --- a/packages/apps/human-app/frontend/src/modules/smart-contracts/Staking/staking-stake.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Contract } from 'ethers'; -import type { ContractCallArguments } from '@/modules/smart-contracts/types'; -import Staking from '@/modules/smart-contracts/abi/Staking.json'; -import { JsonRpcError } from '@/modules/smart-contracts/json-rpc-error'; - -export async function stakingStake({ - contractAddress, - amount, - signer, -}: { - address: string; - amount: string; -} & ContractCallArguments) { - try { - const stakingContract = new Contract(contractAddress, Staking.abi, signer); - const tx = await stakingContract.stake(amount); - await tx.wait(); - } catch (error) { - throw new JsonRpcError(error); - } -} diff --git a/packages/apps/human-app/frontend/src/modules/smart-contracts/get-contract-address.ts b/packages/apps/human-app/frontend/src/modules/smart-contracts/get-contract-address.ts deleted file mode 100644 index ca4783d6d3..0000000000 --- a/packages/apps/human-app/frontend/src/modules/smart-contracts/get-contract-address.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { MainnetChains, TestnetChains } from '@/modules/smart-contracts/chains'; -import type { ContractsAddresses } from '@/modules/smart-contracts/contracts'; -import { env } from '@/shared/env'; - -export const getContractAddress = ({ - contractName, -}: { - contractName: keyof ContractsAddresses; -}): string => { - if (env.VITE_NETWORK === 'testnet') { - return TestnetChains[0].addresses[contractName]; - } - return MainnetChains[0].addresses[contractName]; -}; diff --git a/packages/apps/human-app/frontend/src/modules/smart-contracts/types.ts b/packages/apps/human-app/frontend/src/modules/smart-contracts/types.ts deleted file mode 100644 index de7e0dbe96..0000000000 --- a/packages/apps/human-app/frontend/src/modules/smart-contracts/types.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { BrowserProvider, JsonRpcSigner } from 'ethers'; - -export interface ContractCallArguments { - contractAddress: string; - chainId: number; - provider?: BrowserProvider; - signer?: JsonRpcSigner; -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-form-container.tsx b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-form-container.tsx index 96a31a9294..eea5be3c4b 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-form-container.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-form-container.tsx @@ -1,31 +1,37 @@ -import { useNavigate } from 'react-router-dom'; import { useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PageCard, PageCardLoader } from '@/shared/components/ui/page-card'; +import { Trans, useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; +import { FormProvider } from 'react-hook-form'; +import { Box, Paper, Stack, Typography } from '@mui/material'; + +import { PageCardLoader } from '@/shared/components/ui/page-card'; import { getErrorMessageForError } from '@/shared/errors'; -import { useAuth } from '@/modules/auth/hooks/use-auth'; -import { routerPaths } from '@/router/router-paths'; import { TopNotificationType, useNotification, } from '@/shared/hooks/use-notification'; import { useResendEmailRouterParams, useResendEmail } from '../hooks'; -import { ResendVerificationEmailForm } from './resend-verification-email-form'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { useAuth } from '@/modules/auth/hooks/use-auth'; +import { routerPaths } from '@/router/router-paths'; +import { InboxIcon } from '@/shared/components/ui/icons'; +import { Button } from '@/shared/components/ui/button'; +import { HCaptchaForm } from '@/shared/components/hcaptcha/h-captcha-form'; export function EmailVerificationFormContainer() { - const { user, signOut } = useAuth(); - const navigate = useNavigate(); - const routerState = useResendEmailRouterParams(); + const { email } = useResendEmailRouterParams() ?? {}; const { methods, handleResend, isError, error, isSuccess } = useResendEmail(); const { showNotification } = useNotification(); const { t } = useTranslation(); - const isAuthenticated = Boolean(user); + const { colorPalette } = useColorMode(); + const { user, signOut } = useAuth(); + const navigate = useNavigate(); useEffect(() => { if (isError) { showNotification({ message: getErrorMessageForError(error), - type: TopNotificationType.WARNING, + type: TopNotificationType.ERROR, }); } }, [isError, error, showNotification]); @@ -44,18 +50,107 @@ export function EmailVerificationFormContainer() { navigate(routerPaths.homePage); }; - if (!routerState?.email) { + if (!email) { return ; } return ( - - - + + +
{ + void methods.handleSubmit(handleResend)(event); + }} + > + + + + {t('worker.verifyEmail.checkYourInbox')} + + + + ), + }} + i18nKey="worker.verifyEmail.paragraph1" + values={{ email }} + /> + + + {t('worker.verifyEmail.paragraph2')} + + + + + {!!user && ( + + + + + )} + +
+
+
); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-process.tsx b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-process.tsx index 3aa579253e..2c77e27772 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-process.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-process.tsx @@ -7,13 +7,7 @@ import { getErrorMessageForError } from '@/shared/errors'; import { useVerifyEmailMutation } from '../hooks'; import { EmailVerificationSuccessMessage } from './email-verification-success-message'; -interface EmailVerificationProcessProps { - token: string; -} - -export function EmailVerificationProcess({ - token, -}: Readonly) { +export function EmailVerificationProcess({ token }: { token: string }) { const { error, isError, diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-success-message.tsx b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-success-message.tsx index 1fcd31c0ab..ccd9ec3eff 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-success-message.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/email-verification-success-message.tsx @@ -1,33 +1,58 @@ import { t } from 'i18next'; -import Grid from '@mui/material/Grid'; -import Typography from '@mui/material/Typography'; import { Link } from 'react-router-dom'; +import { Paper, Stack, Typography } from '@mui/material'; + import { Button } from '@/shared/components/ui/button'; -import { PageCard } from '@/shared/components/ui/page-card'; -import { SuccessLabel } from '@/shared/components/ui/success-label'; +import { SuccessIcon } from '@/shared/components/ui/icons'; +import { useColorMode } from '@/shared/contexts/color-mode'; import { routerPaths } from '@/router/router-paths'; export function EmailVerificationSuccessMessage() { + const { colorPalette } = useColorMode(); return ( - {t('worker.emailVerification.title')}} + - - - {t('worker.emailVerification.description')} + + + + {t('worker.emailVerification.title')} - - + + ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/resend-verification-email-form.tsx b/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/resend-verification-email-form.tsx deleted file mode 100644 index 76b22ebd60..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/email-verification/components/resend-verification-email-form.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { FormProvider } from 'react-hook-form'; -import { type z } from 'zod'; -import Grid from '@mui/material/Grid'; -import Typography from '@mui/material/Typography'; -import { Trans, useTranslation } from 'react-i18next'; -import type { UseFormReturn } from 'react-hook-form'; -import { Button } from '@/shared/components/ui/button'; -import { HCaptchaForm } from '@/shared/components/hcaptcha/h-captcha-form'; -import { MailTo } from '@/shared/components/ui/mail-to'; -import { env } from '@/shared/env'; -import { - type resendEmailVerificationHcaptchaSchema, - type ResendEmailVerificationDto, -} from '../schemas'; - -interface ResendVerificationEmailFormProps { - methods: UseFormReturn< - z.input, - unknown, - ResendEmailVerificationDto - >; - handleResend: (data: ResendEmailVerificationDto) => void; - email: string; - isAuthenticated: boolean; -} - -export function ResendVerificationEmailForm({ - methods, - handleResend, - email, - isAuthenticated, -}: Readonly) { - const { t } = useTranslation(); - - return ( - -
{ - void methods.handleSubmit(handleResend)(event); - }} - > - - - , - }} - i18nKey="worker.verifyEmail.paragraph1" - values={{ email }} - /> - - - {t('worker.verifyEmail.paragraph2')} - - - , - }} - i18nKey="worker.verifyEmail.paragraph3" - /> - - - - ), - 2: , - }} - i18nKey="worker.verifyEmail.paragraph4" - /> - - {isAuthenticated && ( - <> - - - - )} - -
-
- ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-email-verification-mutation.ts b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-email-verification-mutation.ts index 8e86287543..0e3fcca435 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-email-verification-mutation.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-email-verification-mutation.ts @@ -1,12 +1,6 @@ -import { z } from 'zod'; import { useMutation } from '@tanstack/react-query'; -import * as emailVerificationService from '../services/email-verification.service'; - -export const verifyEmailDtoSchema = z.object({ - token: z.string(), -}); -export const VerifyEmailSuccessResponseSchema = z.unknown(); +import * as emailVerificationService from '../services/email-verification.service'; export function useVerifyEmailMutation({ token }: { token: string }) { return useMutation({ diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-email-verification-token.ts b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-email-verification-token.ts index 1e8cc8aa3b..67507dfa72 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-email-verification-token.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-email-verification-token.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { useLocationState } from '@/modules/worker/hooks/use-location-state'; -export const tokenSchema = z.string().transform((value, ctx) => { +const tokenSchema = z.string().transform((value, ctx) => { const token = value.split('=')[1]; if (!token) { ctx.addIssue({ diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email-router-params.ts b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email-router-params.ts index ce2b3e8a52..2b3d8e2fae 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email-router-params.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email-router-params.ts @@ -1,7 +1,7 @@ import { z } from 'zod'; import { useLocationState } from '@/modules/worker/hooks/use-location-state'; -export const routerStateSchema = z.object({ +const routerStateSchema = z.object({ email: z.email(), resendOnMount: z.boolean().optional(), }); diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/index.ts b/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/index.ts deleted file mode 100644 index 27efb8dd15..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './worker-email-verification-process.page'; -export * from './worker-verify-email.page'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-email-verification-process.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-email-verification-process.page.tsx index 3ee0ee1c05..56dea183e1 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-email-verification-process.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-email-verification-process.page.tsx @@ -3,7 +3,7 @@ import { PageCardError } from '@/shared/components/ui/page-card'; import { EmailVerificationProcess } from '../components'; import { useEmailVerificationToken } from '../hooks'; -export function WorkerEmailVerificationProcessPage() { +export function EmailVerificationProcessPage() { const { t } = useTranslation(); const { token } = useEmailVerificationToken(); diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-verify-email.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-verify-email.page.tsx index d27a145f3a..69cb33b36c 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-verify-email.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/pages/worker-verify-email.page.tsx @@ -1,5 +1,5 @@ import { EmailVerificationFormContainer } from '../components'; -export function WorkerVerifyEmailPage() { +export function VerifyEmailPage() { return ; } diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/breadcrumbs.tsx b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/breadcrumbs.tsx new file mode 100644 index 0000000000..4c825db3bb --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/breadcrumbs.tsx @@ -0,0 +1,42 @@ +import { Box, Link, Typography } from '@mui/material'; +import { Link as RouterLink } from 'react-router-dom'; +import { t } from 'i18next'; + +import { routerPaths } from '@/router/router-paths'; +import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode'; + +export function Breadcrumbs() { + const { colorPalette } = useColorMode(); + + return ( + + + {t('worker.jobs.availableJobs')} + + + {'>'} + + + {t('worker.jobs.hCaptcha')} + + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/index.ts b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/index.ts deleted file mode 100644 index 7a439e76d9..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './user-stats-accordion'; -export * from './user-stats-details'; -export * from './user-stats-drawer'; -export * from './user-stats-loading-overlay'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-accordion.tsx b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-accordion.tsx index 082157d655..f198e6d185 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-accordion.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-accordion.tsx @@ -1,11 +1,14 @@ -import Accordion from '@mui/material/Accordion'; -import AccordionSummary from '@mui/material/AccordionSummary'; -import AccordionDetails from '@mui/material/AccordionDetails'; -import Typography from '@mui/material/Typography'; -import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; -import Grid from '@mui/material/Grid'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { t } from 'i18next'; +import { + Accordion, + AccordionSummary, + AccordionDetails, + Grid, + Typography, +} from '@mui/material'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; + import { TopNotificationType, useNotification, @@ -16,10 +19,14 @@ import { useHCaptchaUserStats } from '../hooks'; import { UserStatsDetails } from './user-stats-details'; import { LoadingOverlay } from './user-stats-loading-overlay'; -const accordionWidth = { width: '284px' }; +const ACCORDION_WIDTH = '284px'; +const ACCORDION_HEIGHT = '48px'; export function UserStatsAccordion() { + const [isExpanded, setIsExpanded] = useState(false); const { colorPalette } = useColorMode(); + const { showNotification } = useNotification(); + const { data: hcaptchaUserStats, isPending: isHcaptchaUserStatsPending, @@ -28,28 +35,32 @@ export function UserStatsAccordion() { refetch: refetchUserStats, isRefetching: isHcaptchaUserStatsRefetching, } = useHCaptchaUserStats(); - const { showNotification } = useNotification(); useEffect(() => { if (isHcaptchaUserStatsError) { showNotification({ - type: TopNotificationType.WARNING, + type: TopNotificationType.ERROR, message: getErrorMessageForError(hcaptchaUserStatsError), }); } - // eslint-disable-next-line react-hooks/exhaustive-deps -- ... - }, [isHcaptchaUserStatsError, hcaptchaUserStatsError]); + }, [isHcaptchaUserStatsError, hcaptchaUserStatsError, showNotification]); return ( - + { + setIsExpanded(expanded); + }} sx={{ - ...accordionWidth, position: 'relative', overflow: 'hidden', + width: ACCORDION_WIDTH, + minHeight: ACCORDION_HEIGHT, + zIndex: 1, }} > - {isHcaptchaUserStatsRefetching && ( + {isExpanded && isHcaptchaUserStatsRefetching && ( } id="panel1-header" - sx={{ ...accordionWidth, height: '76px' }} + sx={{ + width: ACCORDION_WIDTH, + height: ACCORDION_HEIGHT, + py: 1.5, + '& > .MuiAccordionSummary-content': { + m: 0, + }, + '&.Mui-expanded': { minHeight: ACCORDION_HEIGHT }, + }} > {t('worker.hcaptchaLabelingStats.statistics')} {hcaptchaUserStats ? ( - + void refetchUserStats()} stats={hcaptchaUserStats} diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-details.tsx b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-details.tsx index 36100f1aab..4bb9a0a58e 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-details.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-details.tsx @@ -1,46 +1,40 @@ import { Divider, IconButton, Stack, Typography } from '@mui/material'; import { t } from 'i18next'; + import { RefreshIcon } from '@/shared/components/ui/icons'; -import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette'; import { useColorMode } from '@/shared/contexts/color-mode'; import { useIsMobile } from '@/shared/hooks'; import { type HCaptchaUserStatsSuccess } from '../types'; -export function UserStatsDetails({ - stats, - refetch, - isRefetching, -}: Readonly<{ +type Props = { stats: HCaptchaUserStatsSuccess; refetch: () => void; isRefetching: boolean; -}>) { - const { colorPalette, isDarkMode } = useColorMode(); - const isMobile = useIsMobile(); +}; - const statsColor = isDarkMode - ? onlyDarkModeColor.additionalTextColor - : colorPalette.primary.light; +export function UserStatsDetails({ stats, refetch, isRefetching }: Props) { + const { colorPalette } = useColorMode(); + const isMobile = useIsMobile(); return ( - - - - + {!isMobile && ( + + )} + + + {t('worker.hcaptchaLabelingStats.allTime')} {t('worker.hcaptchaLabelingStats.jobsServed')} - + {stats.served} @@ -48,7 +42,7 @@ export function UserStatsDetails({ {t('worker.hcaptchaLabelingStats.jobsComplete')} - + {stats.solved} @@ -56,43 +50,56 @@ export function UserStatsDetails({ {t('worker.hcaptchaLabelingStats.hmtEarned')} - + {stats.balance.total}{' '} - + {t('inputMasks.humanCurrencySuffix')} - + - + - + {t('worker.hcaptchaLabelingStats.lastHour')} {t('worker.hcaptchaLabelingStats.earnedLastHour')} - + {stats.balance.recent}{' '} - + {t('inputMasks.humanCurrencySuffix')} - + {t('worker.hcaptchaLabelingStats.statisticsNotLive')} diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-drawer.tsx b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-drawer.tsx index 1e584e4322..dcd304ab0e 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-drawer.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-drawer.tsx @@ -1,80 +1,98 @@ -import { Box, CssBaseline, Drawer, Stack, Typography } from '@mui/material'; +import { Box, Drawer, IconButton, Stack, Typography } from '@mui/material'; +import CloseIcon from '@mui/icons-material/Close'; import { t } from 'i18next'; + import { Loader } from '@/shared/components/ui/loader'; import { Alert } from '@/shared/components/ui/alert'; import { getErrorMessageForError } from '@/shared/errors'; import { useHCaptchaUserStats } from '../hooks'; import { UserStatsDetails } from './user-stats-details'; import { LoadingOverlay } from './user-stats-loading-overlay'; +import { useColorMode } from '@/shared/contexts/color-mode'; -export interface UserStatsDrawerNavigationProps { +type Props = { isOpen: boolean; -} + onClose: () => void; +}; + +export function UserStatsDrawer({ isOpen, onClose }: Props) { + const { colorPalette } = useColorMode(); -export function UserStatsDrawer({ - isOpen, -}: Readonly) { const { data: hcaptchaUserStats, error: hcaptchaUserStatsError, - status: hcaptchaUserStatsStatus, + isSuccess, + isPending, refetch: hcaptchaUserStatsRefetch, isRefetching: isHcaptchaUserStatsRefetching, } = useHCaptchaUserStats(); return ( - - - theme.zIndex.drawer, + '& .MuiDrawer-paper': { width: '100%', - flexShrink: 0, - '& .MuiDrawer-paper': { - width: '100%', - paddingTop: 11, - }, - }} - > - - {isHcaptchaUserStatsRefetching && ( - + + {isHcaptchaUserStatsRefetching && ( + + )} + + + {t('worker.hcaptchaLabelingStats.hCapchaStatistics')} + + + + + + + {isSuccess && ( + void hcaptchaUserStatsRefetch()} + stats={hcaptchaUserStats} + isRefetching={isHcaptchaUserStatsRefetching} /> )} - - {hcaptchaUserStatsStatus === 'success' ? ( - <> - - {t('worker.hcaptchaLabelingStats.hCapchaStatistics')} - - void hcaptchaUserStatsRefetch()} - stats={hcaptchaUserStats} - isRefetching={isHcaptchaUserStatsRefetching} - /> - - ) : null} - {hcaptchaUserStatsStatus === 'error' ? ( - - {getErrorMessageForError(hcaptchaUserStatsError)} - - ) : null} - {hcaptchaUserStatsStatus === 'pending' ? ( - - ) : null} - - - - + {!!hcaptchaUserStatsError && ( + + {getErrorMessageForError(hcaptchaUserStatsError)} + + )} + {isPending && } + + + ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/enable-labeler.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/enable-labeler.page.tsx index 338d41c3a8..b8315d7aa4 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/enable-labeler.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/enable-labeler.page.tsx @@ -1,86 +1,100 @@ -import Grid from '@mui/material/Grid'; -import Paper from '@mui/material/Paper'; +import { useEffect } from 'react'; import { t } from 'i18next'; -import Typography from '@mui/material/Typography'; -import { Navigate } from 'react-router-dom'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { Box, Paper, Stack, Typography } from '@mui/material'; + import { Button } from '@/shared/components/ui/button'; -import { PageCardError } from '@/shared/components/ui/page-card'; import { getErrorMessageForError } from '@/shared/errors'; -import { breakpoints } from '@/shared/styles/breakpoints'; -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; -import { routerPaths } from '@/router/router-paths'; +import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode'; +import { + TopNotificationType, + useNotification, +} from '@/shared/hooks/use-notification'; import { useEnableHCaptchaLabelingMutation } from './hooks'; +import { Breadcrumbs } from './components/breadcrumbs'; export function EnableLabelerPage() { - const isMobile = useIsMobile(); - const { user } = useAuthenticatedUser(); - const { mutate, error, isError, isPending } = + const { colorPalette } = useColorMode(); + const { showNotification } = useNotification(); + const { mutate, error, isError, isPending, reset } = useEnableHCaptchaLabelingMutation(); - if (!user.wallet_address) { - return ; - } - - if (isError) { - return ; - } + useEffect(() => { + if (isError) { + showNotification({ + message: getErrorMessageForError(error), + type: TopNotificationType.ERROR, + }); + reset(); + } + }, [error, isError, showNotification, reset]); return ( - - + + + - - - + {t('worker.enableHCaptchaLabeling.description')} - - - - - + + + + ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hcaptcha-labeling.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hcaptcha-labeling.page.tsx index 3efadc29da..f278b5b862 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hcaptcha-labeling.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hcaptcha-labeling.page.tsx @@ -1,21 +1,16 @@ -import HCaptcha from '@hcaptcha/react-hcaptcha'; -import Grid from '@mui/material/Grid'; -import { Paper, Typography } from '@mui/material'; +import { useRef, useState } from 'react'; import { t } from 'i18next'; -import { Navigate, useNavigate } from 'react-router-dom'; -import { useRef } from 'react'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { useNavigate, Link as RouterLink } from 'react-router-dom'; +import HCaptcha from '@hcaptcha/react-hcaptcha'; +import { Box, Divider, Paper, Stack, Typography } from '@mui/material'; + import { env } from '@/shared/env'; -import { breakpoints } from '@/shared/styles/breakpoints'; import { Counter } from '@/shared/components/ui/counter'; import { getErrorMessageForError } from '@/shared/errors'; import { getTomorrowDate } from '@/shared/helpers/date'; import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; -import { useWorkerIdentityVerificationStatus } from '@/modules/worker/profile/hooks'; import { useHCaptchaLabelingNotifications } from '@/modules/worker/hooks/use-hcaptcha-labeling-notifications'; import { useColorMode } from '@/shared/contexts/color-mode'; -import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette'; -import { routerPaths } from '@/router/router-paths'; import { PageCardLoader, PageCardError, @@ -25,22 +20,23 @@ import { useDailyHmtSpent, useSolveHCaptchaMutation, } from './hooks'; +import { Breadcrumbs } from './components/breadcrumbs'; +import { Button } from '@/shared/components/ui/button'; +import { routerPaths } from '@/router/router-paths'; +import { UserStatsAccordion } from './components/user-stats-accordion'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { UserStatsDrawer } from './components/user-stats-drawer'; export function HcaptchaLabelingPage() { - const { colorPalette, isDarkMode } = useColorMode(); + const [isStatsDrawerOpen, setIsStatsDrawerOpen] = useState(false); const captchaRef = useRef(null); + + const { colorPalette } = useColorMode(); const { user } = useAuthenticatedUser(); - const { isVerificationCompleted } = useWorkerIdentityVerificationStatus(); - const { onSuccess, onError } = useHCaptchaLabelingNotifications(); - const statsColor = isDarkMode - ? onlyDarkModeColor.additionalTextColor - : colorPalette.primary.light; + const navigate = useNavigate(); + const isMobile = useIsMobile(); - const resetCaptcha = () => { - if (captchaRef.current) { - captchaRef.current.resetCaptcha(); - } - }; + const { onSuccess, onError } = useHCaptchaLabelingNotifications(); const { mutate: solveHCaptchaMutation } = useSolveHCaptchaMutation({ onSuccess: () => { @@ -67,8 +63,8 @@ export function HcaptchaLabelingPage() { error: dailyHmtSpentError, } = useDailyHmtSpent(); - const isMobile = useIsMobile(); - const navigate = useNavigate(); + const isError = isDailyHmtSpentError || isHcaptchaUserStatsError; + const isPending = isHcaptchaUserStatsPending || isDailyHmtSpentPending; const canSolveCaptcha = dailyHmtSpent && @@ -81,97 +77,189 @@ export function HcaptchaLabelingPage() { solveHCaptchaMutation({ token }); }; - if (!isVerificationCompleted) { - return ; - } - - if (isHcaptchaUserStatsPending || isDailyHmtSpentPending) { - return ; - } - - if (isHcaptchaUserStatsError || isDailyHmtSpentError) { - return ( - - ); - } + const resetCaptcha = () => { + if (captchaRef.current) { + captchaRef.current.resetCaptcha(); + } + }; return ( - - - + {isMobile ? ( + + ) : ( + + )} + + + - - - {t('worker.hcaptchaLabeling.description')} - - {canSolveCaptcha ? ( - - - - ) : ( - - - {t('worker.hcaptchaLabeling.noJobs')} - - - { - navigate(0); + {isPending && !isError && } + {isError && ( + + )} + {!isPending && !isError && ( + + {canSolveCaptcha ? ( + <> + - - - )} - - - - + > + {t('worker.hcaptchaLabeling.description')} + + + + + + ) : ( + + + {t('worker.hcaptchaLabeling.noJobs')} + + + + + {t('worker.hcaptchaLabeling.waitFor')} + + + { + navigate(0); + }} + /> + + + + + + {t('worker.hcaptchaLabeling.or')} + + + + + + + + )} + + )} + + + setIsStatsDrawerOpen(false)} + /> + ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/enable-hcaptcha-labeling.ts b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/enable-hcaptcha-labeling.ts index fcc7c9ccdd..15718a5692 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/enable-hcaptcha-labeling.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/enable-hcaptcha-labeling.ts @@ -1,5 +1,6 @@ import { useMutation } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; + import { routerPaths } from '@/router/router-paths'; import { useAccessTokenRefresh } from '@/api/hooks/use-access-token-refresh'; import * as hCaptchaLabelingService from '../services/hcaptcha-labeling.service'; @@ -7,16 +8,15 @@ import * as hCaptchaLabelingService from '../services/hcaptcha-labeling.service' export function useEnableHCaptchaLabelingMutation() { const navigate = useNavigate(); const { refreshAccessTokenAsync } = useAccessTokenRefresh(); + const mutation = useMutation({ mutationFn: async () => { const result = await hCaptchaLabelingService.enableHCaptchaLabeling(); - - await refreshAccessTokenAsync({ authType: 'web2' }); - + await refreshAccessTokenAsync(); return result; }, onSuccess: () => { - navigate(routerPaths.worker.HcaptchaLabeling); + navigate(routerPaths.HcaptchaLabeling); }, }); diff --git a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-exchange-api-keys.ts b/packages/apps/human-app/frontend/src/modules/worker/hooks/use-exchange-api-keys.ts deleted file mode 100644 index c958791ca7..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-exchange-api-keys.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { - deleteExchangeApiKeys, - enrollExchangeApiKeys, - getExchangeApiKeys, - getSupportedExchanges, -} from '../services/exchangeApiKeys.service'; - -function useGetSupportedExchanges() { - return useQuery({ - queryKey: ['supported-exchanges'], - queryFn: () => getSupportedExchanges(), - }); -} - -function useGetExchangeApiKeys() { - return useQuery({ - queryKey: ['exchange-api-keys'], - queryFn: () => getExchangeApiKeys(), - }); -} - -function useEnrollExchangeApiKeys() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ['enroll-exchange-api-keys'], - mutationFn: (data: { - exchange: string; - apiKey: string; - secretKey: string; - }) => enrollExchangeApiKeys(data), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['exchange-api-keys'] }); - queryClient.invalidateQueries({ queryKey: ['staking-summary'] }); - }, - }); -} - -function useDeleteExchangeApiKeys() { - const queryClient = useQueryClient(); - - return useMutation({ - mutationKey: ['delete-exchange-api-keys'], - mutationFn: () => deleteExchangeApiKeys(), - onSuccess: () => { - queryClient.invalidateQueries({ queryKey: ['exchange-api-keys'] }); - queryClient.invalidateQueries({ queryKey: ['staking-summary'] }); - }, - }); -} - -export { - useGetSupportedExchanges, - useDeleteExchangeApiKeys, - useGetExchangeApiKeys, - useEnrollExchangeApiKeys, -}; diff --git a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-get-oracles.ts b/packages/apps/human-app/frontend/src/modules/worker/hooks/use-get-oracles.ts index 345439d159..d9b328013c 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-get-oracles.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/hooks/use-get-oracles.ts @@ -1,12 +1,10 @@ import { useQuery } from '@tanstack/react-query'; -import { useJobsTypesOraclesFilterStore } from '../jobs/hooks'; + import * as oraclesService from '../services/oracles.service'; export function useGetOracles() { - const { selectedJobTypes } = useJobsTypesOraclesFilterStore(); - return useQuery({ - queryFn: async () => oraclesService.getOracles(selectedJobTypes), - queryKey: ['oracles', selectedJobTypes], + queryFn: async () => oraclesService.getOracles(), + queryKey: ['oracles'], }); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-register-address.ts b/packages/apps/human-app/frontend/src/modules/worker/hooks/use-register-address.ts index 428b2d592e..f0d9d622a6 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-register-address.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/hooks/use-register-address.ts @@ -1,12 +1,12 @@ import { useMutation } from '@tanstack/react-query'; import { t } from 'i18next'; -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; import { useAccessTokenRefresh } from '@/api/hooks/use-access-token-refresh'; import type { ResponseError } from '@/shared/types/global.type'; import { useWalletConnect } from '@/shared/contexts/wallet-connect'; import { usePrepareSignature } from '@/shared/hooks'; import { PrepareSignatureType } from '@/shared/services/signature.service'; import * as profileService from '../profile/services/profile.service'; +import { useAuth } from '@/modules/auth/hooks/use-auth'; interface RegisterAddressCallbacks { onSuccess: () => void | Promise; @@ -14,7 +14,7 @@ interface RegisterAddressCallbacks { } function useRegisterAddressMutation(callbacks: RegisterAddressCallbacks) { - const { user, updateUserData } = useAuthenticatedUser(); + const { user, updateUserData } = useAuth(); const { refreshAccessTokenAsync } = useAccessTokenRefresh(); const { address, chainId, signMessage } = useWalletConnect(); const { prepareSignature } = usePrepareSignature( @@ -37,7 +37,7 @@ function useRegisterAddressMutation(callbacks: RegisterAddressCallbacks) { // wallet address is part of the JWT payload // so we need to refresh the token after the address is registered await profileService.registerAddress({ address, chainId, signature }); - await refreshAccessTokenAsync({ authType: 'web2' }); + await refreshAccessTokenAsync(); updateUserData({ wallet_address: address, }); @@ -51,7 +51,7 @@ function useRegisterAddressMutation(callbacks: RegisterAddressCallbacks) { onError: async (error: ResponseError) => { await callbacks.onError(error); }, - mutationKey: [user.wallet_address], + mutationKey: [user?.wallet_address], }); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-staking.ts b/packages/apps/human-app/frontend/src/modules/worker/hooks/use-staking.ts deleted file mode 100644 index 1556c072d6..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-staking.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { getStakingSummary } from '../services/staking.service'; - -function useGetStakingSummary() { - return useQuery({ - queryKey: ['staking-summary'], - queryFn: () => getStakingSummary(), - }); -} - -export { useGetStakingSummary }; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/explore-tasks-dialog.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/explore-tasks-dialog.tsx new file mode 100644 index 0000000000..204dfef5c1 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/explore-tasks-dialog.tsx @@ -0,0 +1,96 @@ +import { useTranslation } from 'react-i18next'; +import { Stack, Typography } from '@mui/material'; + +import { ResponsiveOverlay } from '@/shared/components/ui/responsive-overlay'; +import { Oracle } from '../../services/oracles.service'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { shortenEscrowAddress } from '@/shared/helpers/evm'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { AvailableJobsTableDesktop } from '../../jobs/available-jobs/components/desktop'; +import { AvailableJobsListMobile } from '../../jobs/available-jobs/components/mobile'; + +type Props = { + open: boolean; + onClose: () => void; + oracle: Oracle; +}; + +export function ExploreTasksDialog({ open, onClose, oracle }: Props) { + const { colorPalette } = useColorMode(); + const { t } = useTranslation(); + const isMobile = useIsMobile(); + + return ( + + + + {t('worker.jobs.jobsFor')}{' '} + + {oracle.name} + + + + {t('worker.jobs.address')}:{' '} + + {shortenEscrowAddress(oracle.address, 4, 4)} + + + + + {isMobile ? ( + + ) : ( + + )} + + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/hcaptcha-widget.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/hcaptcha-widget.tsx new file mode 100644 index 0000000000..2b9804bbad --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/hcaptcha-widget.tsx @@ -0,0 +1,267 @@ +import { useMemo } from 'react'; +import { Box, Stack, Typography } from '@mui/material'; +import { Link, useNavigate } from 'react-router-dom'; +import { t } from 'i18next'; + +import { env } from '@/shared/env'; +import { Button } from '@/shared/components/ui/button'; +import { + HcaptchaDisabledIcon, + HcaptchaIcon, +} from '@/shared/components/ui/icons'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { routerPaths } from '@/router/router-paths'; +import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { + useHCaptchaUserStats, + useDailyHmtSpent, +} from '../../hcaptcha-labeling/hooks'; +import { getTomorrowDate } from '@/shared/helpers/date'; +import { Counter } from '@/shared/components/ui/counter'; +import { Loader } from '@/shared/components/ui/loader'; + +const getHCaptchaPagePath = (siteKey: string | null | undefined): string => + siteKey ? routerPaths.HcaptchaLabeling : routerPaths.enableLabeler; + +const renderDescription = (color: string, isMobile: boolean) => { + let description = ''; + + if (isMobile) { + description = t('worker.hcaptchaWidget.shortDescription'); + } else { + description = t('worker.hcaptchaWidget.description'); + } + + return ( + + {description} + + ); +}; + +export function HCaptchaWidget() { + const { colorPalette } = useColorMode(); + const { user } = useAuthenticatedUser(); + const isMobile = useIsMobile(); + const navigate = useNavigate(); + + const { data: hcaptchaUserStats, isPending: isHcaptchaUserStatsPending } = + useHCaptchaUserStats(); + + const { data: dailyHmtSpent, isPending: isDailyHmtSpentPending } = + useDailyHmtSpent(); + + const hCaptchaPagePath = useMemo( + () => getHCaptchaPagePath(user.site_key), + [user.site_key] + ); + + if (isHcaptchaUserStatsPending || isDailyHmtSpentPending) { + return ( + + + + ); + } + + const isCaptchaLimitReached = + !!dailyHmtSpent && + !!hcaptchaUserStats && + hcaptchaUserStats.currentDateStats.solved >= + env.VITE_DAILY_SOLVED_CAPTCHA_LIMIT && + dailyHmtSpent.spend >= env.VITE_HMT_DAILY_SPENT_LIMIT; + + return ( + + {isCaptchaLimitReached ? ( + <> + + + + + {t('worker.hcaptchaWidget.titleDisabled')} + + {!isMobile && ( + + {t('worker.hcaptchaWidget.descriptionDisabled')} + + )} + + + + + {isMobile + ? t('worker.hcaptchaWidget.nextIn') + : t('worker.hcaptchaWidget.nextAvailable')} + + + { + navigate(0); + }} + /> + + + + ) : ( + <> + + + + + {t('worker.hcaptchaWidget.title')} + + {!isMobile && + renderDescription(colorPalette.text.auxiliary200, isMobile)} + + + + {isMobile && + renderDescription(colorPalette.text.auxiliary200, isMobile)} + + + + )} + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/index.ts deleted file mode 100644 index 9bc965d5af..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './oracles-table-job-types-select'; -export * from './oracles-table'; -export * from './oracles-table-item-mobile'; -export * from './oracles-table-desktop'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracle-job-card.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracle-job-card.tsx new file mode 100644 index 0000000000..418d03869c --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracle-job-card.tsx @@ -0,0 +1,313 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { + Box, + Card, + CardContent, + Chip, + Collapse, + Stack, + Tooltip, + Typography, +} from '@mui/material'; +import { t } from 'i18next'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; +import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; + +import { Oracle } from '../../services/oracles.service'; +import { + MenuIcon, + OracleAddressIcon, + OracleRewardIcon, +} from '@/shared/components/ui/icons'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { CopyToClipboardButton } from '@/shared/components/ui/copy-to-clipboard-button'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { Button } from '@/shared/components/ui/button'; +import { JOB_TYPES } from '@/shared/consts'; +import { JobType } from '@/shared/types/entity.type'; +import { ExploreTasksDialog } from './explore-tasks-dialog'; +import { EvmAddress, RewardAmount } from '../../jobs/components'; +import { useGetRegistrationDataInOracles } from '../hooks/use-get-registration-data-oracles'; +import { shouldNavigateToRegistration } from '../helpers'; +import { routerPaths } from '@/router/router-paths'; + +function JobTypesTooltipTitle({ jobTypes }: { jobTypes: string[] }) { + return ( + + {jobTypes.map((jobType) => { + const element = JOB_TYPES.find((j) => j === jobType) as JobType; + const label = t(`jobTypeLabels.${element}`); + return ( + + {label} + + ); + })} + + ); +} + +export function OracleJobCard({ oracle }: { oracle: Oracle }) { + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [isTaskTypesOpen, setIsTaskTypesOpen] = useState(false); + + const navigate = useNavigate(); + const { colorPalette } = useColorMode(); + const isMobile = useIsMobile(); + + const { data, isLoading } = useGetRegistrationDataInOracles(); + + const isRewardAmountsEqual = + oracle.minRewardAmount === oracle.maxRewardAmount; + const hasTasks = oracle.nTasks > 0; + + const handleClickOnExploreTasks = () => { + if (isLoading) return; + + if (shouldNavigateToRegistration(oracle, data)) { + navigate(`${routerPaths.registrationInExchangeOracle}/${oracle.address}`); + return; + } + + setIsDialogOpen(true); + }; + + return ( + + + + {oracle.name} + + + + + + {t('worker.oraclesList.address')}: + + + + + + + + {t('worker.oraclesList.reward')}: + + + {isRewardAmountsEqual ? ( + + ) : ( + <> + + -{' '} + + + )} + + + + + + {isMobile ? ( + + ) : ( + } + disableHoverListener={isMobile || !hasTasks} + slotProps={{ + tooltip: { + sx: { + p: 1.5, + bgcolor: colorPalette.background.paper, + boxShadow: 'none', + borderRadius: '15px', + border: `1px solid ${colorPalette.border.main}`, + }, + }, + }} + > + + + {oracle.nTasks}{' '} + {oracle.nTasks === 1 + ? t('worker.oraclesList.task') + : t('worker.oraclesList.tasks')} + + + )} + + + + + {oracle.jobTypes.map((jobType) => { + const element = JOB_TYPES.find((j) => j === jobType) as JobType; + const label = t(`jobTypeLabels.${element}`); + return ( + + ); + })} + + + + + setIsDialogOpen(false)} + oracle={oracle} + /> + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-list.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-list.tsx new file mode 100644 index 0000000000..2713b8b605 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-list.tsx @@ -0,0 +1,57 @@ +import { Grid, Stack } from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +import { Loader } from '@/shared/components/ui/loader'; +import { NoRecords } from '@/shared/components/ui/no-records'; +import { PageCardError } from '@/shared/components/ui/page-card'; +import { OracleJobCard } from './oracle-job-card'; +import { Oracle } from '../../services/oracles.service'; + +type OraclesListProps = { + data: Oracle[] | undefined; + isError: boolean; + isPending: boolean; +}; + +export function OraclesList({ data, isError, isPending }: OraclesListProps) { + const { t } = useTranslation(); + + if (isPending) { + return ( + + + + ); + } + + if (isError) { + return ( + + ); + } + + if (!data?.length) { + return ; + } + + return ( + + {data.map((oracle) => ( + + + + ))} + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-desktop.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-desktop.tsx deleted file mode 100644 index 712e80b411..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-desktop.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { - MaterialReactTable, - useMaterialReactTable, -} from 'material-react-table'; -import { createTableDarkMode } from '@/shared/styles/create-table-dark-mode'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { useOraclesTableColumns } from '../hooks/use-oracles-table-columns'; -import { type Oracle } from '../../services/oracles.service'; - -interface OraclesTableDesktopProps { - isOraclesDataPending: boolean; - isOraclesDataError: boolean; - oraclesData: Oracle[]; -} - -export function OraclesTableDesktop({ - isOraclesDataPending, - isOraclesDataError, - oraclesData, -}: Readonly) { - const { colorPalette, isDarkMode } = useColorMode(); - const tableColumns = useOraclesTableColumns(); - const table = useMaterialReactTable({ - state: { - isLoading: isOraclesDataPending, - showAlertBanner: isOraclesDataError, - }, - columns: tableColumns, - data: oraclesData, - enableColumnActions: false, - enableColumnFilters: false, - enableSorting: false, - enablePagination: false, - enableTopToolbar: false, - enableBottomToolbar: false, - muiTableHeadCellProps: { - sx: { - borderColor: colorPalette.paper.text, - }, - }, - muiTableBodyCellProps: { - sx: { - borderColor: colorPalette.paper.text, - }, - }, - muiTablePaperProps: { - sx: { - boxShadow: '0px 2px 2px 0px #E9EBFA80', - }, - }, - ...(isDarkMode ? createTableDarkMode(colorPalette) : {}), - }); - - return ; -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-item-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-item-mobile.tsx deleted file mode 100644 index 085b952125..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-item-mobile.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { Grid, Paper, type SxProps, Typography } from '@mui/material'; -import { t } from 'i18next'; -import { Chips } from '@/shared/components/ui/chips'; -import { TableButton } from '@/shared/components/ui/table-button'; -import { ListItem } from '@/shared/components/ui/list-item'; -import type { JobType } from '@/modules/smart-contracts/EthKVStore/config'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { useSelectOracleNavigation } from '../hooks/use-select-oracle-navigation'; -import { EvmAddress } from '../../jobs/components'; -import { type Oracle } from '../../services/oracles.service'; - -interface OraclesTableItemMobileProps { - oracle: Oracle; -} - -const styles: SxProps = { - px: '16px', - py: '32px', - marginBottom: '20px', - borderRadius: '20px', - display: 'flex', - flexDirection: 'column', - gap: '1rem', - boxShadow: 'none', -}; - -export function OraclesTableItemMobile({ - oracle, -}: Readonly) { - const { colorPalette } = useColorMode(); - const { selectOracle } = useSelectOracleNavigation(); - - return ( - - - - - - - {oracle.name} - - - - t(`jobTypeLabels.${jobType as JobType}`) - )} - /> - - - { - selectOracle(oracle); - }} - > - {t('worker.oraclesTable.seeJobs')} - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-job-types-select.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-job-types-select.tsx deleted file mode 100644 index d41c0a9f36..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table-job-types-select.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { t } from 'i18next'; -import { FormProvider, useForm } from 'react-hook-form'; -import { useEffect } from 'react'; -import { Grid } from '@mui/material'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { MultiSelect } from '@/shared/components/data-entry/multi-select'; -import { JOB_TYPES } from '@/shared/consts'; -import { useJobsTypesOraclesFilterStore } from '../../jobs/hooks'; - -export function OraclesTableJobTypesSelect() { - const isMobile = useIsMobile(); - const { selectJobType } = useJobsTypesOraclesFilterStore(); - const methods = useForm<{ jobType: string[] }>({ - defaultValues: { - jobType: [], - }, - }); - - const selectedJobType = methods.watch('jobType'); - - useEffect(() => { - selectJobType(selectedJobType); - }, [selectJobType, selectedJobType]); - - return ( - - -
- ({ - label: t(`jobTypeLabels.${jobType}`), - value: jobType, - }))} - /> - -
-
- ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table.tsx deleted file mode 100644 index abb5fe312c..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { Stack, Grid } from '@mui/material'; -import { useEffect } from 'react'; -import { useTranslation } from 'react-i18next'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { Loader } from '@/shared/components/ui/loader'; -import { NoRecords } from '@/shared/components/ui/no-records'; -import { useGetOraclesNotifications } from '@/modules/worker/hooks/use-get-oracles-notifications'; -import { PageCardError } from '@/shared/components/ui/page-card'; -import { useGetOracles } from '../../hooks'; -import { OraclesTableItemMobile } from './oracles-table-item-mobile'; -import { OraclesTableDesktop } from './oracles-table-desktop'; - -export function OraclesTable() { - const isMobile = useIsMobile(); - const { t } = useTranslation(); - const { onError } = useGetOraclesNotifications(); - const { - data: oraclesData, - isError: isOraclesDataError, - isPending: isOraclesDataPending, - error: oraclesDataError, - } = useGetOracles(); - - useEffect(() => { - if (oraclesDataError) { - onError(oraclesDataError); - } - }, [oraclesDataError, onError]); - - if (isOraclesDataPending) { - return ( - - - - ); - } - - if (isOraclesDataError) { - return ( - - ); - } - - if (!oraclesData.length) { - return ; - } - - return isMobile ? ( - - {oraclesData.map((oracle) => ( - - ))} - - ) : ( - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/index.ts index 7db6869470..c7ea564552 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/index.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/index.ts @@ -1,2 +1 @@ -export * from './is-hcaptcha-oracle'; export * from './should-navigate-to-registration'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.spec.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.spec.ts deleted file mode 100644 index 117e9daaf9..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.spec.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; -import { faker } from '@faker-js/faker'; -import { env } from '@/shared/env'; -import { isHCaptchaOracle } from './is-hcaptcha-oracle'; - -vi.mock('@/shared/env', () => ({ - env: { - VITE_H_CAPTCHA_ORACLE_ADDRESS: '0x1234567890abcdef1234567890abcdef12345678', - }, -})); - -describe('isHCaptchaOracle Helper', () => { - it('should return true when the address matches the hCaptcha oracle address', () => { - const result = isHCaptchaOracle( - '0x1234567890abcdef1234567890abcdef12345678' - ); - expect(result).toBe(true); - }); - - it('should return false when the address does not match the hCaptcha oracle address', () => { - const result = isHCaptchaOracle(faker.finance.ethereumAddress()); - expect(result).toBe(false); - }); - - it('should handle case sensitivity correctly', () => { - const mixedCaseAddress = env.VITE_H_CAPTCHA_ORACLE_ADDRESS.replace( - 'abcdef', - 'ABCDEF' - ); - - const result = isHCaptchaOracle(mixedCaseAddress); - expect(result).toBe(true); - }); - - it('should handle empty strings', () => { - const result = isHCaptchaOracle(''); - expect(result).toBe(false); - }); -}); diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.ts deleted file mode 100644 index 9b5cefc8a3..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { env } from '@/shared/env'; - -export const isHCaptchaOracle = (address: string): boolean => - address.toLowerCase() === env.VITE_H_CAPTCHA_ORACLE_ADDRESS.toLowerCase(); diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.spec.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.spec.ts index 133385f694..a1eb43e1e1 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.spec.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.spec.ts @@ -12,6 +12,10 @@ describe('shouldNavigateToRegistration Helper', () => { url: faker.internet.url(), jobTypes: [faker.word.noun()], name: faker.company.name(), + nTasks: faker.number.int({ min: 1, max: 10 }), + minRewardAmount: faker.finance.amount(), + maxRewardAmount: faker.finance.amount(), + rewardToken: faker.finance.currencyCode(), }; const oracleWithNoRegistration: Oracle = { diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/index.ts deleted file mode 100644 index e3515417de..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './use-oracles-table-columns'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-oracles-table-columns.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-oracles-table-columns.tsx deleted file mode 100644 index abd22f746c..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-oracles-table-columns.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useMemo } from 'react'; -import { t } from 'i18next'; -import { Grid } from '@mui/material'; -import type { MRT_ColumnDef } from 'material-react-table'; -import { Chips } from '@/shared/components/ui/chips'; -import { TableButton } from '@/shared/components/ui/table-button'; -import { type JobType } from '@/modules/smart-contracts/EthKVStore/config'; -import { EvmAddress } from '../../jobs/components'; -import { type Oracle } from '../../services/oracles.service'; -import { useSelectOracleNavigation } from './use-select-oracle-navigation'; - -export const useOraclesTableColumns = (): MRT_ColumnDef[] => { - const { selectOracle } = useSelectOracleNavigation(); - - return useMemo( - () => [ - { - accessorKey: 'name', - header: t('worker.oraclesTable.annotationTool'), - size: 100, - enableSorting: false, - }, - { - accessorKey: 'address', - header: t('worker.oraclesTable.oracleAddress'), - size: 100, - enableSorting: true, - Cell: (props) => , - }, - { - accessorKey: 'jobTypes', - header: t('worker.oraclesTable.jobTypes'), - size: 100, - enableSorting: false, - Cell: ({ row }) => { - const jobTypes = row.original.jobTypes.map((jobType) => - t(`jobTypeLabels.${jobType as JobType}`) - ); - return ; - }, - }, - { - accessorKey: 'url', - id: 'seeJobsAction', - header: '', - size: 100, - enableSorting: false, - Cell: (props) => ( - - { - selectOracle(props.row.original); - }} - > - {t('worker.oraclesTable.seeJobs')} - - - ), - }, - ], - [selectOracle] - ); -}; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-select-oracle-navigation.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-select-oracle-navigation.ts deleted file mode 100644 index 1c81650c57..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-select-oracle-navigation.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { useNavigate } from 'react-router-dom'; -import { useCallback, useMemo } from 'react'; -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; -import { routerPaths } from '@/router/router-paths'; -import { shouldNavigateToRegistration, isHCaptchaOracle } from '../helpers'; -import { type Oracle } from '../../services/oracles.service'; -import { useGetRegistrationDataInOracles } from './use-get-registration-data-oracles'; - -const getHCaptchaPagePath = (siteKey: string | null | undefined): string => - siteKey - ? routerPaths.worker.HcaptchaLabeling - : routerPaths.worker.enableLabeler; - -export const useSelectOracleNavigation = () => { - const navigate = useNavigate(); - const { user } = useAuthenticatedUser(); - const { data } = useGetRegistrationDataInOracles(); - - const hCaptchaPagePath = useMemo( - () => getHCaptchaPagePath(user.site_key), - [user.site_key] - ); - - const selectOracle = useCallback( - (oracle: Oracle) => { - if (shouldNavigateToRegistration(oracle, data)) { - navigate( - `${routerPaths.worker.registrationInExchangeOracle}/${oracle.address}` - ); - return; - } - - if (isHCaptchaOracle(oracle.address)) { - navigate(hCaptchaPagePath); - return; - } - - navigate(`${routerPaths.worker.jobs}/${oracle.address}`, { - state: { oracle }, - }); - }, - [data, navigate, hCaptchaPagePath] - ); - - return { selectOracle }; -}; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/jobs-discovery.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/jobs-discovery.page.tsx index 4f5aefa0db..f3a1f6e851 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/jobs-discovery.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/jobs-discovery.page.tsx @@ -1,35 +1,107 @@ -import Paper from '@mui/material/Paper'; -import Grid from '@mui/material/Grid'; -import { Navigate } from 'react-router-dom'; +import { useEffect } from 'react'; +import { Box, Stack, Typography } from '@mui/material'; +import { t } from 'i18next'; + +import { OraclesList } from './components/oracles-list'; +import { HCaptchaWidget } from './components/hcaptcha-widget'; +import { useColorMode } from '@/shared/contexts/color-mode'; import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { useWorkerIdentityVerificationStatus } from '@/modules/worker/profile/hooks'; -import { routerPaths } from '@/router/router-paths'; -import { OraclesTableJobTypesSelect, OraclesTable } from './components'; +import { useGetOracles } from '../hooks/use-get-oracles'; +import { useGetOraclesNotifications } from '../hooks/use-get-oracles-notifications'; +import { JobsSwitcherMobile } from '@/router/components/layout/protected/jobs-switcher-mobile'; + +const bull = ( + + • + +); export function JobsDiscoveryPage() { + const { colorPalette } = useColorMode(); const isMobile = useIsMobile(); - const { isVerificationCompleted } = useWorkerIdentityVerificationStatus(); - if (!isVerificationCompleted) { - return ; - } + const { onError } = useGetOraclesNotifications(); + const { + data: oraclesData, + isError, + isPending, + isSuccess, + error, + } = useGetOracles(); + + useEffect(() => { + if (error) { + onError(error); + } + }, [error, onError]); + + const oraclesCount = oraclesData?.length ?? 0; return ( - - - - - - - - + + + + + {isMobile && ( + + + + )} + + + + {t('worker.oraclesList.jobOracles')} + + {isSuccess && ( + <> + {bull} + + {oraclesCount}{' '} + {oraclesCount === 1 + ? t('worker.oraclesList.source') + : t('worker.oraclesList.sources')} + + + )} + + + + ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/available-jobs-filter-modal.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/available-jobs-filter-modal.tsx deleted file mode 100644 index 087a512020..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/available-jobs-filter-modal.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import Box from '@mui/material/Box'; -import Drawer from '@mui/material/Drawer'; -import CssBaseline from '@mui/material/CssBaseline'; -import { Divider, IconButton, Stack, Typography } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import CloseIcon from '@mui/icons-material/Close'; -import { HumanLogoIcon } from '@/shared/components/ui/icons'; -import { useHandleMainNavIconClick } from '@/shared/hooks/use-handle-main-nav-icon-click'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { - AvailableJobsNetworkFilter, - AvailableJobsJobTypeFilter, -} from './components'; -import { AvailableJobsRewardAmountSortMobile } from './components/mobile/available-jobs-reward-amount-sort-mobile'; - -interface AvailableJobsFilterModalProps { - chainIdsEnabled: number[]; - close: () => void; -} -export function AvailableJobsFilterModal({ - chainIdsEnabled, - close, -}: Readonly) { - const handleMainNavIconClick = useHandleMainNavIconClick(); - const { colorPalette } = useColorMode(); - const { t } = useTranslation(); - - return ( - - - - - - - - - - - - - - {t('worker.jobs.mobileFilterDrawer.filters')} - - - - {t('worker.jobs.mobileFilterDrawer.sortBy')} - - - - {t('worker.jobs.mobileFilterDrawer.filters')} - - - - {t('worker.jobs.network')} - - - - - - - - {t('worker.jobs.jobType')} - - - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/available-jobs-view.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/available-jobs-view.tsx deleted file mode 100644 index ac645716d3..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/available-jobs-view.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { AvailableJobsTableDesktop } from './components/desktop'; -import { AvailableJobsTableMobile } from './components/mobile'; - -interface AvailableJobsTableView { - chainIdsEnabled: number[]; -} - -export function AvailableJobsView({ chainIdsEnabled }: AvailableJobsTableView) { - const isMobile = useIsMobile(); - - return isMobile ? ( - - ) : ( - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/available-jobs-job-type-filter.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/available-jobs-job-type-filter.tsx deleted file mode 100644 index 893ed6a4a4..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/available-jobs-job-type-filter.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { useMemo } from 'react'; -import { Filtering } from '@/shared/components/ui/table/table-header-menu/filtering'; -import { JOB_TYPES } from '@/shared/consts'; -import { useJobsFilterStore } from '../../hooks'; - -export function AvailableJobsJobTypeFilter({ - showClearButton = false, - showTitle = false, -}) { - const { t } = useTranslation(); - const { setFilterParams, filterParams } = useJobsFilterStore(); - - const filteringOptions = useMemo( - () => - JOB_TYPES.map((jobType) => ({ - name: t(`jobTypeLabels.${jobType}`), - option: jobType, - })), - [t] - ); - - const handleClear = () => { - setFilterParams({ job_type: undefined }); - }; - - const handleFilterChange = (jobType: string) => { - setFilterParams({ job_type: jobType }); - }; - - return ( - option === filterParams.job_type} - setFiltering={handleFilterChange} - showClearButton={showClearButton} - showTitle={showTitle} - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/available-jobs-network-filter.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/available-jobs-network-filter.tsx deleted file mode 100644 index 79399bceac..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/available-jobs-network-filter.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Filtering } from '@/shared/components/ui/table/table-header-menu/filtering'; -import { useGetAllNetworks, useJobsFilterStore } from '../../hooks'; - -interface AvailableJobsNetworkFilterProps { - chainIdsEnabled: number[]; - showClearButton?: boolean; - showTitle?: boolean; -} - -export function AvailableJobsNetworkFilter({ - chainIdsEnabled, - showClearButton = false, - showTitle = false, -}: Readonly) { - const { setFilterParams, filterParams } = useJobsFilterStore(); - const { allNetworks } = useGetAllNetworks(chainIdsEnabled); - - const handleClear = () => { - setFilterParams({ chain_id: undefined }); - }; - - const handleFilterChange = (chainId: number) => { - setFilterParams({ chain_id: chainId }); - }; - - return ( - option === filterParams.chain_id} - setFiltering={handleFilterChange} - showClearButton={showClearButton} - showTitle={showTitle} - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/available-jobs-reward-amount-sort.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/available-jobs-reward-amount-sort.tsx deleted file mode 100644 index b2825107b6..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/available-jobs-reward-amount-sort.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { t } from 'i18next'; -import { Sorting } from '@/shared/components/ui/table/table-header-menu/sorting'; -import { useJobsFilterStore } from '../../hooks'; -import { SortDirection, SortField } from '../../types'; - -export function AvailableJobsRewardAmountSort() { - const { setFilterParams } = useJobsFilterStore(); - - const sortAscRewardAmount = () => { - setFilterParams({ - sort_field: SortField.REWARD_AMOUNT, - sort: SortDirection.ASC, - }); - }; - - const sortDescRewardAmount = () => { - setFilterParams({ - sort_field: SortField.REWARD_AMOUNT, - sort: SortDirection.DESC, - }); - }; - - return ( - { - setFilterParams({ - sort_field: undefined, - sort: undefined, - }); - }} - sortingOptions={[ - { - label: t('worker.jobs.sortDirection.fromHighest'), - sortCallback: sortDescRewardAmount, - }, - { - label: t('worker.jobs.sortDirection.fromLowest'), - sortCallback: sortAscRewardAmount, - }, - ]} - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/desktop/available-jobs-table-desktop.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/desktop/available-jobs-table-desktop.tsx index b20ecf2e28..0a9373e4d1 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/desktop/available-jobs-table-desktop.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/desktop/available-jobs-table-desktop.tsx @@ -1,37 +1,32 @@ +import { useEffect, useMemo } from 'react'; import { MaterialReactTable, useMaterialReactTable, } from 'material-react-table'; -import { t } from 'i18next'; -import { useEffect, useMemo } from 'react'; + import { createTableDarkMode } from '@/shared/styles/create-table-dark-mode'; import { useColorMode } from '@/shared/contexts/color-mode'; import { useJobsFilterStore } from '../../../hooks'; -import { EscrowAddressSearchForm } from '../../../components'; import { useGetAvailableJobsData } from '../../hooks/use-get-available-jobs-data'; import { useGetAvailableJobsColumns } from '../../hooks'; import { useAvailableJobsPagination } from '../../hooks/use-available-jobs-pagination'; -interface AvailableJobsTableProps { - chainIdsEnabled: number[]; -} - export function AvailableJobsTableDesktop({ - chainIdsEnabled, -}: Readonly) { + oracleAddress, +}: { + oracleAddress: string; +}) { const { colorPalette, isDarkMode } = useColorMode(); - const { data: tableData, status: tableStatus } = useGetAvailableJobsData(); - const { - setSearchEscrowAddress, - setPageParams, - filterParams, - resetFilterParams, - } = useJobsFilterStore(); + const { data: tableData, status: tableStatus } = useGetAvailableJobsData({ + oracleAddress, + }); + const { setPageParams, filterParams, resetFilterParams } = + useJobsFilterStore(); const { paginationState, setPaginationState } = useAvailableJobsPagination({ setPageParams, filterParams, }); - const columns = useGetAvailableJobsColumns(chainIdsEnabled); + const columns = useGetAvailableJobsColumns(); const memoizedTableDataResults = useMemo( () => tableData?.results ?? [], @@ -52,51 +47,85 @@ export function AvailableJobsTableDesktop({ showAlertBanner: tableStatus === 'error', pagination: paginationState, }, - enablePagination: Boolean(tableData?.total_pages), + enablePagination: !!tableData?.total_pages, manualPagination: true, onPaginationChange: setPaginationState, muiPaginationProps: { SelectProps: { sx: { + '.MuiSelect-select': { + color: colorPalette.text.auxiliary100, + }, '.MuiSelect-icon': { ':hover': { backgroundColor: 'blue', }, - fill: colorPalette.text.primary, + fill: colorPalette.text.auxiliary100, }, }, }, rowsPerPageOptions: [5, 10], }, + muiBottomToolbarProps: { + sx: { + bgcolor: colorPalette.background.paper, + boxShadow: 'none', + color: colorPalette.text.auxiliary100, + }, + }, pageCount: tableData?.total_pages ?? -1, rowCount: tableData?.total_results, enableColumnActions: false, enableColumnFilters: false, - enableSorting: true, - manualSorting: true, - renderTopToolbar: () => ( - { - setSearchEscrowAddress(address); - }} - /> - ), + enableSorting: false, + manualSorting: false, + renderTopToolbar: false, + muiTablePaperProps: { + sx: { + boxShadow: 'none', + }, + }, + muiTableHeadProps: { + sx: { + backgroundColor: colorPalette.background.default, + }, + }, + muiTableHeadRowProps: { + sx: { + backgroundColor: 'inherit', + boxShadow: 'none', + }, + }, muiTableHeadCellProps: { sx: { - borderColor: colorPalette.paper.text, + paddingTop: '8px', + paddingBottom: '8px', + paddingLeft: '12px', + paddingRight: '12px', + borderColor: colorPalette.background.paper, + color: colorPalette.text.auxiliary200, + typography: 'body1', + fontWeight: 500, + '& .Mui-TableHeadCell-Content': { + justifyContent: 'center', + textAlign: 'center', + }, }, }, muiTableBodyCellProps: { + align: 'center', sx: { - borderColor: colorPalette.paper.text, + textAlign: 'center', + borderBottom: `1px solid ${colorPalette.border.main}`, }, }, - muiTablePaperProps: { + muiTableBodyRowProps: { sx: { - boxShadow: '0px 2px 2px 0px #E9EBFA80', + bgcolor: colorPalette.background.paper, + borderBottom: `1px solid ${colorPalette.border.main}`, + '&:last-of-type': { + borderBottom: 'none', + }, }, }, ...(isDarkMode ? createTableDarkMode(colorPalette) : {}), diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/index.ts deleted file mode 100644 index 9d9f97fed7..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './available-jobs-job-type-filter'; -export * from './available-jobs-network-filter'; -export * from './available-jobs-reward-amount-sort'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-assign-job-button-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-assign-job-button-mobile.tsx deleted file mode 100644 index 3acd1102e0..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-assign-job-button-mobile.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { t } from 'i18next'; -import { TableButton } from '@/shared/components/ui/table-button'; -import { useJobsNotifications } from '../../../hooks'; -import { useAssignJobMutation } from '../../hooks/use-assign-job'; -import { type AssignJobBody } from '../../../types'; - -export function AvailableJobsAssignJobButtonMobile({ - assignJobPayload, -}: Readonly<{ - assignJobPayload: AssignJobBody; -}>) { - const { onJobAssignmentError, onJobAssignmentSuccess } = - useJobsNotifications(); - - const { mutate: assignJobMutation, isPending } = useAssignJobMutation( - { - onSuccess: onJobAssignmentSuccess, - onError: onJobAssignmentError, - }, - [`assignJob-${assignJobPayload.escrow_address}`] - ); - - return ( - assignJobMutation(assignJobPayload)} - size="small" - sx={{ - marginTop: '15px', - }} - type="button" - variant="contained" - > - {t('worker.jobs.selectJob')} - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-card.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-card.tsx new file mode 100644 index 0000000000..6c53d44187 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-card.tsx @@ -0,0 +1,186 @@ +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Chip, Paper, Stack, Typography } from '@mui/material'; + +import { AvailableJob } from '../../../types'; +import { JobType } from '@/shared/types/entity.type'; +import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode'; +import { + OracleAddressIcon, + OracleRewardIcon, +} from '@/shared/components/ui/icons'; +import { EvmAddress, RewardAmount } from '../../../components'; +import { Button } from '@/shared/components/ui/button'; +import { useJobsNotifications } from '../../../hooks'; +import { useAssignJobMutation } from '../../hooks'; +import { BaseDrawer } from '@/shared/components/ui/base-drawer'; +import { ChainIcon } from '@/shared/components/ui/chain-icon'; + +function DescriptionDrawer({ + open, + onClose, + description, +}: { + open: boolean; + onClose: () => void; + description: string | undefined; +}) { + const { t } = useTranslation(); + const { colorPalette } = useColorMode(); + + return ( + + + {t('worker.jobs.taskDescriptionTitle')} + + + {description} + + + ); +} + +export function AvailableJobsCard({ job }: { job: AvailableJob }) { + const [showDescriptionDialog, setShowDescriptionDialog] = useState(false); + + const { t } = useTranslation(); + const { colorPalette } = useColorMode(); + + const { onJobAssignmentError, onJobAssignmentSuccess } = + useJobsNotifications(); + + const { mutate: assignJobMutation, isPending } = useAssignJobMutation( + { + onSuccess: onJobAssignmentSuccess, + onError: onJobAssignmentError, + }, + [`assignJob-${job.escrow_address}`] + ); + + return ( + + + + + + + + + + + {t('worker.oraclesList.address')}: + + + + + + + {t('worker.oraclesList.reward')}: + + + + + + + {!!job.job_description && ( + + )} + + + setShowDescriptionDialog(false)} + description={job.job_description} + /> + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-list-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-list-mobile.tsx index 0ece918fc8..25945747a9 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-list-mobile.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-list-mobile.tsx @@ -1,37 +1,29 @@ -import { Grid, List, Paper, Stack, Typography } from '@mui/material'; -import { useTranslation } from 'react-i18next'; import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Stack } from '@mui/material'; + import { Button } from '@/shared/components/ui/button'; import { Alert } from '@/shared/components/ui/alert'; -import { getNetworkName } from '@/modules/smart-contracts/get-network-name'; import { Loader } from '@/shared/components/ui/loader'; -import { Chip } from '@/shared/components/ui/chip'; -import { ListItem } from '@/shared/components/ui/list-item'; -import { type JobType } from '@/modules/smart-contracts/EthKVStore/config'; -import { useColorMode } from '@/shared/contexts/color-mode'; import { getErrorMessageForError } from '@/shared/errors'; import { useCombinePages } from '@/shared/hooks'; import { useJobsFilterStore } from '../../../hooks'; import { useInifiniteGetAvailableJobsData } from '../../hooks/use-get-available-jobs-data'; -import { EvmAddress, RewardAmount } from '../../../components'; import { type AvailableJob } from '../../../types'; -import { AvailableJobsAssignJobButtonMobile } from './available-jobs-assign-job-button-mobile'; +import { AvailableJobsCard } from './available-jobs-card'; -export function AvailableJobsListMobile() { +export function AvailableJobsListMobile({ + oracleAddress, +}: { + oracleAddress: string; +}) { const { t } = useTranslation(); - const { colorPalette } = useColorMode(); const { filterParams, setPageParams, resetFilterParams } = useJobsFilterStore(); - const { - data: tableData, - status: tableStatus, - isError: isTableError, - error: tableError, - fetchNextPage, - hasNextPage, - } = useInifiniteGetAvailableJobsData(); + const { data, isPending, isError, error, fetchNextPage, hasNextPage } = + useInifiniteGetAvailableJobsData({ oracleAddress }); - const allPages = useCombinePages(tableData, filterParams.page); + const allPages = useCombinePages(data); useEffect(() => { return () => { @@ -40,97 +32,31 @@ export function AvailableJobsListMobile() { }, [resetFilterParams]); return ( - - {isTableError && ( + + {isError && ( - {getErrorMessageForError(tableError)} + {getErrorMessageForError(error)} )} - {tableStatus === 'pending' && ( - + {isPending && ( + )} - {allPages.map((d) => ( - - - - - - - {d.job_description} - - - - - - - - - - - - - - - {getNetworkName(d.chain_id)} - - - - - - - - - - - - - - + ))} - {hasNextPage ? ( + {hasNextPage && ( - ) : null} + )} ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-reward-amount-sort-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-reward-amount-sort-mobile.tsx deleted file mode 100644 index 5aa9807f7f..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-reward-amount-sort-mobile.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { t } from 'i18next'; -import Typography from '@mui/material/Typography'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { useJobsFilterStore } from '../../../hooks'; -import { Sorting } from '../../../components'; -import { SortDirection, SortField } from '../../../types'; - -export function AvailableJobsRewardAmountSortMobile() { - const { setFilterParams, filterParams } = useJobsFilterStore(); - const { colorPalette } = useColorMode(); - - return ( - - {t('worker.jobs.rewardAmount')} - - } - fromHighestSelected={ - filterParams.sort_field === SortField.REWARD_AMOUNT && - filterParams.sort === SortDirection.DESC - } - sortFromHighest={() => { - setFilterParams({ - sort: SortDirection.DESC, - sort_field: SortField.REWARD_AMOUNT, - }); - }} - fromLowestSelected={ - filterParams.sort_field === SortField.REWARD_AMOUNT && - filterParams.sort === SortDirection.ASC - } - sortFromLowest={() => { - setFilterParams({ - sort: SortDirection.ASC, - sort_field: SortField.REWARD_AMOUNT, - }); - }} - clear={() => { - setFilterParams({ - sort: undefined, - sort_field: undefined, - }); - }} - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-table-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-table-mobile.tsx deleted file mode 100644 index fdefb14217..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-table-mobile.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { Button } from '@/shared/components/ui/button'; -import { FiltersButtonIcon } from '@/shared/components/ui/icons'; -import { useJobsFilterStore } from '../../../hooks'; -import { EscrowAddressSearchForm } from '../../../components'; -import { useAvailableJobsFilterModal } from '../../hooks/use-available-jobs-filter-modal'; -import { AvailableJobsListMobile } from './available-jobs-list-mobile'; - -export function AvailableJobsTableMobile() { - const { t } = useTranslation(); - const { setSearchEscrowAddress } = useJobsFilterStore(); - const { openModal } = useAvailableJobsFilterModal(); - - return ( - <> - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/index.ts index 04265ea0ed..f6d103b75d 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/index.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/index.ts @@ -1,2 +1 @@ -export * from './available-jobs-reward-amount-sort-mobile'; -export * from './available-jobs-table-mobile'; +export * from './available-jobs-list-mobile'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-assign-job.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-assign-job.ts index 0f31b2da5e..1680542116 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-assign-job.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-assign-job.ts @@ -1,10 +1,8 @@ -import { z } from 'zod'; import { type MutationKey, useMutation } from '@tanstack/react-query'; + import * as jobsService from '../../services/jobs.service'; import { type AssignJobBody } from '../../types'; -export const AssignJobBodySuccessResponseSchema = z.unknown(); - export function useAssignJobMutation( callbacks?: { onSuccess: () => void; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-available-jobs-filter-modal.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-available-jobs-filter-modal.tsx deleted file mode 100644 index 6ce55c5c48..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-available-jobs-filter-modal.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useModal } from '@/shared/contexts/modal-context'; -import { useUiConfig } from '@/shared/providers/ui-config-provider'; -import { AvailableJobsFilterModal } from '../available-jobs-filter-modal'; - -export function useAvailableJobsFilterModal() { - const { openModal, closeModal } = useModal(); - const { uiConfig } = useUiConfig(); - - return { - openModal: () => { - openModal({ - content: ( - - ), - }); - }, - }; -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-get-available-jobs-columns.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-get-available-jobs-columns.tsx index 753ad47200..a5860da416 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-get-available-jobs-columns.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-get-available-jobs-columns.tsx @@ -1,39 +1,33 @@ +import { useMemo } from 'react'; import type { MRT_ColumnDef } from 'material-react-table'; import { t } from 'i18next'; -import { Grid } from '@mui/material'; -import { useMemo } from 'react'; -import { getNetworkName } from '@/modules/smart-contracts/get-network-name'; -import { Chip } from '@/shared/components/ui/chip'; +import { Box, Chip, Stack, Typography } from '@mui/material'; + import { TableButton } from '@/shared/components/ui/table-button'; -import { TableHeaderCell } from '@/shared/components/ui/table/table-header-cell'; -import type { JobType } from '@/modules/smart-contracts/EthKVStore/config'; +import type { JobType } from '@/shared/types/entity.type'; import { useJobsNotifications } from '../../hooks'; import { EvmAddress, RewardAmount } from '../../components'; -import { - AvailableJobsNetworkFilter, - AvailableJobsRewardAmountSort, - AvailableJobsJobTypeFilter, -} from '../components'; import { type AvailableJob } from '../../types'; import { useAssignJobMutation } from './use-assign-job'; +import { Button } from '@/shared/components/ui/button'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { useModal } from '@/shared/contexts/modal-context'; +import { ChainIcon } from '@/shared/components/ui/chain-icon'; + +const COL_SIZE = 50; +const COL_SIZE_MD = 100; +const COL_SIZE_LG = 150; +const COL_SIZE_XL = 250; -const COL_SIZE = 100; -const COL_SIZE_LG = 200; +export const useGetAvailableJobsColumns = (): MRT_ColumnDef[] => { + const { colorPalette } = useColorMode(); + const { openModal } = useModal(); -export const useGetAvailableJobsColumns = ( - chainIdsEnabled: number[] -): MRT_ColumnDef[] => { return useMemo( () => [ - { - accessorKey: 'job_description', - header: t('worker.jobs.jobDescription'), - size: COL_SIZE, - enableSorting: false, - }, { accessorKey: 'escrow_address', - header: t('worker.jobs.escrowAddress'), + header: t('worker.jobs.address'), size: COL_SIZE, enableSorting: false, Cell: (props) => { @@ -46,26 +40,13 @@ export const useGetAvailableJobsColumns = ( size: COL_SIZE, enableSorting: false, Cell: (props) => { - return getNetworkName(props.row.original.chain_id); + return ; }, - Header: ( - - } - /> - ), }, { accessorKey: 'reward_amount', - header: t('worker.jobs.rewardAmount'), - size: COL_SIZE, + header: t('worker.jobs.reward'), + size: COL_SIZE_MD, enableSorting: false, Cell: (props) => { const { reward_amount, reward_token } = props.row.original; @@ -73,16 +54,10 @@ export const useGetAvailableJobsColumns = ( ); }, - Header: ( - } - /> - ), }, { accessorKey: 'job_type', @@ -91,26 +66,31 @@ export const useGetAvailableJobsColumns = ( enableSorting: false, Cell: ({ row }) => { const label = t(`jobTypeLabels.${row.original.job_type as JobType}`); - return ; + return ( + + ); }, - Header: ( - - } - /> - ), }, { accessorKey: 'escrow_address', id: 'selectJobAction', - header: '', - size: COL_SIZE, + header: t('worker.jobs.action'), + size: COL_SIZE_XL, enableSorting: false, Cell: (props) => { - const { escrow_address, chain_id } = props.row.original; + const { escrow_address, chain_id, job_description } = + props.row.original; const { onJobAssignmentError, onJobAssignmentSuccess } = useJobsNotifications(); const { mutate: assignJobMutation, isPending } = useAssignJobMutation( @@ -120,21 +100,53 @@ export const useGetAvailableJobsColumns = ( }, [`assignJob-${escrow_address}`] ); + const description = job_description?.trim() || ''; + + const handleOpenTaskDescription = () => { + openModal({ + content: ( + + + {t('worker.jobs.jobDescription')} + + + {description} + + + ), + }); + }; return ( - + + {description && ( + + )} assignJobMutation({ escrow_address, chain_id })} > - {t('worker.jobs.selectJob')} + {t('worker.jobs.claimTask')} - + ); }, }, ], - [chainIdsEnabled] + [colorPalette, openModal] ); }; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-get-available-jobs-data.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-get-available-jobs-data.ts index 077ffc0408..643044b2e1 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-get-available-jobs-data.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-get-available-jobs-data.ts @@ -1,34 +1,31 @@ import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; -import { useParams } from 'react-router-dom'; -import { useJobsFilterStore } from '../../hooks'; + import * as jobsService from '../../services/jobs.service'; import { type AvailableJobsSuccessResponse } from '../../types'; -export function useGetAvailableJobsData() { - const { filterParams } = useJobsFilterStore(); - const { address: oracle_address } = useParams<{ address: string }>(); - const queryParams = { ...filterParams, oracle_address: oracle_address ?? '' }; +type Props = { + oracleAddress?: string; +}; + +export function useGetAvailableJobsData({ oracleAddress }: Props) { + const queryParams = { oracle_address: oracleAddress }; return useQuery({ queryKey: ['availableJobs', queryParams], queryFn: async ({ signal }) => jobsService.fetchAvailableJobs({ queryParams, signal }), + enabled: !!oracleAddress, }); } -export function useInifiniteGetAvailableJobsData() { - const { filterParams } = useJobsFilterStore(); - const { address: oracleAddress } = useParams<{ address: string }>(); - - const queryParams = { - ...filterParams, - oracle_address: oracleAddress ?? '', - }; +export function useInifiniteGetAvailableJobsData({ oracleAddress }: Props) { + const queryParams = { oracle_address: oracleAddress }; return useInfiniteQuery({ queryKey: ['availableJobsInfinite', queryParams], queryFn: async ({ signal }) => jobsService.fetchAvailableJobs({ queryParams, signal }), + enabled: !!oracleAddress, initialPageParam: 0, getNextPageParam: (lastPage) => lastPage.total_pages - 1 <= lastPage.page ? undefined : lastPage.page, diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/index.ts deleted file mode 100644 index 2d3fbc8ac2..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './available-jobs-view'; -export * from './available-jobs-filter-modal'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/escrow-address-search-form.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/escrow-address-search-form.tsx deleted file mode 100644 index 77cebcfca9..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/escrow-address-search-form.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import Search from '@mui/icons-material/Search'; -import InputAdornment from '@mui/material/InputAdornment'; -import { FormProvider, useForm } from 'react-hook-form'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { z } from 'zod'; -import { useEffect, useMemo } from 'react'; -import Grid from '@mui/material/Grid'; -import debounce from 'lodash/debounce'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { Input } from '@/shared/components/data-entry/input'; -import { addressSchemaOrEmptyString } from '@/shared/schemas'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; - -interface SearchFormProps { - label: string; - placeholder: string; - columnId: string; - fullWidth?: boolean; - updater: (fieldValue: string) => void; -} - -export function EscrowAddressSearchForm({ - label, - placeholder, - updater, - fullWidth = false, -}: Readonly) { - const isMobile = useIsMobile(); - const { colorPalette } = useColorMode(); - const methods = useForm({ - defaultValues: { - searchValue: '', - }, - resolver: zodResolver( - z.object({ searchValue: addressSchemaOrEmptyString }) - ), - }); - - const debouncedUpdater = useMemo( - () => - debounce(async (value: string) => { - const isValid = await methods.trigger('searchValue'); - if (isValid) { - updater(value); - } - }, 500), - [updater, methods] - ); - - useEffect(() => { - return () => { - debouncedUpdater.cancel(); - }; - }, [debouncedUpdater]); - - useEffect(() => { - const subscription = methods.watch((value, { name }) => { - if (name === 'searchValue') { - void debouncedUpdater(value.searchValue ?? ''); - } - }); - return () => { - subscription.unsubscribe(); - }; - }, [debouncedUpdater, methods]); - - return ( - - - - - - ), - }} - fullWidth - label={label} - name="searchValue" - onChange={(e) => { - methods.setValue('searchValue', e.target.value); - }} - placeholder={placeholder} - sx={{ - width: fullWidth ? '100%' : '362px', - margin: fullWidth ? '0' : '1rem', - }} - onBlur={() => { - void methods.trigger('searchValue'); - }} - /> - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/evm-address.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/evm-address.tsx index 1370dd854a..6d2e704c0c 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/evm-address.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/evm-address.tsx @@ -1,7 +1,6 @@ -import Tooltip from '@mui/material/Tooltip'; -import Typography from '@mui/material/Typography'; +import { Tooltip, Typography } from '@mui/material'; + import { shortenEscrowAddress } from '@/shared/helpers/evm'; -import { breakpoints } from '@/shared/styles/breakpoints'; import { useIsMobile } from '@/shared/hooks/use-is-mobile'; import { useColorMode } from '@/shared/contexts/color-mode'; @@ -9,19 +8,13 @@ export function EvmAddress({ address }: { address: string }) { const { colorPalette } = useColorMode(); const isMobile = useIsMobile(); - const shortAddress = isMobile - ? shortenEscrowAddress(address, 4, 4) - : shortenEscrowAddress(address); + const shortAddress = shortenEscrowAddress(address, 4, 4); return ( {shortAddress} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/index.ts index ca383549de..4cffc4caf3 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/index.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/index.ts @@ -1,8 +1,5 @@ export * from './evm-address'; -export * from './jobs-tab-panel'; export * from './my-jobs-table-actions'; -export * from './escrow-address-search-form'; export * from './reward-amount'; -export * from './sorting'; export * from './more-button'; -export * from './report-abuse-modal'; +export * from './report-abuse-dialog'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/jobs-tab-panel.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/jobs-tab-panel.tsx deleted file mode 100644 index b87990ffbf..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/jobs-tab-panel.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { Box } from '@mui/material'; - -interface TabPanelProps { - children?: React.ReactNode; - index: number; - activeTab: number; -} - -export function TabPanel({ children, index, activeTab }: TabPanelProps) { - return ( - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/more-button.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/more-button.tsx index a79920ed10..d8674df036 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/more-button.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/more-button.tsx @@ -1,14 +1,15 @@ import { useState } from 'react'; import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; import { Button, MenuList, ListItemButton, Popover } from '@mui/material'; -import { useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { useModal } from '@/shared/contexts/modal-context'; + import { useIsMobile } from '@/shared/hooks/use-is-mobile'; import { TopNotificationType, useNotification } from '@/shared/hooks'; import { useResignJobMutation } from '../my-jobs/hooks'; import { type MyJob } from '../schemas'; -import { ReportAbuseModal } from './report-abuse-modal'; +import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode'; +import { useMyJobsFilterStore } from '../hooks'; +import { ReportAbuseDialog } from './report-abuse-dialog'; interface MoreButtonProps { job: MyJob; @@ -17,20 +18,23 @@ interface MoreButtonProps { export function MoreButton({ job, isDisabled }: MoreButtonProps) { const [anchorEl, setAnchorEl] = useState(null); - const { address: oracleAddress } = useParams<{ address: string }>(); - const { mutateAsync: rejectTaskMutation } = useResignJobMutation(); - const { openModal, closeModal } = useModal(); - const isMobile = useIsMobile(); + const [isDialogOpen, setIsDialogOpen] = useState(false); + + const { colorPalette } = useColorMode(); const { t } = useTranslation(); + const isMobile = useIsMobile(); const { showNotification } = useNotification(); + const { filterParams } = useMyJobsFilterStore(); + const { mutateAsync: rejectTaskMutation } = useResignJobMutation(); + const isOpen = Boolean(anchorEl); const handleCancelTask = async () => { setAnchorEl(null); try { await rejectTaskMutation({ - oracle_address: oracleAddress ?? '', + oracle_address: filterParams.oracle_address ?? '', assignment_id: job.assignment_id, }); } catch { @@ -44,16 +48,7 @@ export function MoreButton({ job, isDisabled }: MoreButtonProps) { const handleOpenReportAbuseModal = () => { setAnchorEl(null); - openModal({ - content: ( - - ), - showCloseButton: false, - }); + setIsDialogOpen(true); }; return ( @@ -62,12 +57,12 @@ export function MoreButton({ job, isDisabled }: MoreButtonProps) { disabled={isDisabled} sx={{ minWidth: 'unset', - width: { xs: '48px', md: '30px' }, - height: { xs: '48px', md: '30px' }, + width: { xs: '44px', md: '30px' }, + height: { xs: '44px', md: '30px' }, p: 1, - border: isMobile ? '1px solid #858ec6' : 'none', + border: { xs: `1px solid ${colorPalette.border.main}`, md: 'none' }, borderRadius: '4px', - color: '#858ec6', + color: colorPalette.text.auxiliary100, }} onClick={(e) => { if (!isDisabled) { @@ -95,7 +90,7 @@ export function MoreButton({ job, isDisabled }: MoreButtonProps) { paper: { elevation: 8, sx: { - mt: isMobile ? -1 : 1, + mt: { xs: -1, md: 1 }, }, }, }} @@ -109,6 +104,12 @@ export function MoreButton({ job, isDisabled }: MoreButtonProps) { + setIsDialogOpen(false)} + escrowAddress={job.escrow_address} + chainId={job.chain_id} + /> ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/my-jobs-table-actions.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/my-jobs-table-actions.tsx index 886dc72875..7501dcb0b4 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/my-jobs-table-actions.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/my-jobs-table-actions.tsx @@ -1,17 +1,16 @@ import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; + import { TableButton } from '@/shared/components/ui/table-button'; import { MyJobStatus } from '../types'; import { type MyJob } from '../schemas'; import { MoreButton } from './more-button'; -interface MyJobsTableRejectActionProps { +type Props = { job: MyJob; -} +}; -export function MyJobsTableActions({ - job, -}: Readonly) { +export function MyJobsTableActions({ job }: Props) { const { t } = useTranslation(); const isDisabled = job.status !== MyJobStatus.ACTIVE; @@ -28,7 +27,7 @@ export function MyJobsTableActions({ target="_blank" to={job.url} sx={{ - height: { xs: '48px', md: '30px' }, + height: { xs: '44px', md: '30px' }, maxWidth: { xs: 'unset', sm: '160px' }, flex: { xs: 1, md: 'unset' }, }} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/report-abuse-dialog.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/report-abuse-dialog.tsx new file mode 100644 index 0000000000..9e2bcfd6f3 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/report-abuse-dialog.tsx @@ -0,0 +1,260 @@ +import { useState } from 'react'; +import { + Box, + FormControl, + MenuItem, + Select, + Stack, + Typography, +} from '@mui/material'; +import ErrorIcon from '@mui/icons-material/Error'; +import SuccessIcon from '@mui/icons-material/CheckCircle'; +import { useTranslation } from 'react-i18next'; + +import { ResponsiveOverlay } from '@/shared/components/ui/responsive-overlay'; +import { useReportAbuseMutation } from '../available-jobs/hooks/use-report-abuse'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { Button } from '@/shared/components/ui/button'; +import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode'; +import { Loader } from '@/shared/components/ui/loader'; + +type Props = { + open: boolean; + onClose: () => void; + escrowAddress: string; + chainId: number; +}; + +const ABUSE_ERROR = 'Abuse has already been reported'; + +const REASON_OPTIONS = [ + 'sexual_content', + 'nudity', + 'violence', + 'gore', + 'hate_or_racism', + 'drugs', + 'terrorism', + 'child_abuse', + 'self_harm', + 'weapons', + 'criminal_activity', +] as const; + +function ErrorState({ error }: { error: string }) { + const { t } = useTranslation(); + const { colorPalette } = useColorMode(); + + const isAbuseError = error === ABUSE_ERROR; + const errorColor = colorPalette.error.main; + + return ( + + + {isAbuseError ? ( + <> + + {t('worker.reportAbuse.modalHeaderAlreadyReportedError')} + + + {t('worker.reportAbuse.modalParagraphAlreadyReportedError')} + + + ) : ( + + {t('worker.reportAbuse.modalUnknownError')} + + )} + + ); +} + +function SuccessState() { + const { t } = useTranslation(); + const { colorPalette } = useColorMode(); + + return ( + + + + {t('worker.reportAbuse.modalSuccessHeader')} + + + {t('worker.reportAbuse.modalSuccessParagraph')} + + + ); +} + +export function ReportAbuseDialog({ + open, + onClose, + escrowAddress, + chainId, +}: Props) { + const [reason, setReason] = useState(''); + const [error, setError] = useState(''); + + const { t } = useTranslation(); + const { colorPalette } = useColorMode(); + const isMobile = useIsMobile(); + + const { + mutate: reportAbuseMutation, + isSuccess, + isError, + isIdle, + isPending, + } = useReportAbuseMutation({ + onError: (status) => { + if (status === 422) { + setError(ABUSE_ERROR); + } else { + setError('Something went wrong'); + } + }, + }); + + const isIdleOrLoading = isIdle || isPending; + + const handleReportAbuse = () => { + if (!reason.length) { + return; + } + + reportAbuseMutation({ + escrow_address: escrowAddress, + chain_id: chainId, + reason, + }); + }; + + return ( + + + + {t('worker.reportAbuse.modalHeader')} + + {isIdleOrLoading && ( + <> + + {t('worker.reportAbuse.modalParagraph')} + + + + + + )} + + {isPending && ( + + + + )} + {isError && } + {isSuccess && } + + + + + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/report-abuse-modal.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/report-abuse-modal.tsx deleted file mode 100644 index 7db3ca047b..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/report-abuse-modal.tsx +++ /dev/null @@ -1,190 +0,0 @@ -import { useState } from 'react'; -import { - Box, - Button, - CircularProgress, - FormControl, - MenuItem, - Select as MuiSelect, - Stack, - Typography, -} from '@mui/material'; -import ErrorIcon from '@mui/icons-material/Error'; -import SuccessIcon from '@mui/icons-material/CheckCircle'; -import { useTranslation } from 'react-i18next'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { colorPalette } from '@/shared/styles/color-palette'; -import { useReportAbuseMutation } from '../available-jobs/hooks/use-report-abuse'; - -interface ReportAbuseModalProps { - escrowAddress: string; - chainId: number; - close: () => void; -} - -const ABUSE_ERROR = 'Abuse has already been reported'; - -const REASON_OPTIONS = [ - 'sexual_content', - 'nudity', - 'violence', - 'gore', - 'hate_or_racism', - 'drugs', - 'terrorism', - 'child_abuse', - 'self_harm', - 'weapons', - 'criminal_activity', -] as const; - -function ErrorState({ error }: { error: string }) { - const { t } = useTranslation(); - - const isAbuseError = error === ABUSE_ERROR; - const errorColor = colorPalette.error.main; - - return ( - - - {isAbuseError ? ( - <> - - {t('worker.reportAbuse.modalHeaderAlreadyReportedError')} - - - {t('worker.reportAbuse.modalParagraphAlreadyReportedError')} - - - ) : ( - - {t('worker.reportAbuse.modalUnknownError')} - - )} - - ); -} - -function SuccessState() { - const { t } = useTranslation(); - return ( - - - - {t('worker.reportAbuse.modalSuccessHeader')} - - - {t('worker.reportAbuse.modalSuccessParagraph')} - - - ); -} - -export function ReportAbuseModal({ - escrowAddress, - chainId, - close, -}: ReportAbuseModalProps) { - const [reason, setReason] = useState(''); - const [error, setError] = useState(''); - const isMobile = useIsMobile(); - const { t } = useTranslation(); - - const { - mutate: reportAbuseMutation, - isSuccess, - isError, - isIdle, - isPending, - } = useReportAbuseMutation({ - onError: (status) => { - if (status === 422) { - setError(ABUSE_ERROR); - } else { - setError('Something went wrong'); - } - }, - }); - - const isIdleOrLoading = isIdle || isPending; - - const handleReportAbuse = () => { - if (!reason.length) { - return; - } - - reportAbuseMutation({ - escrow_address: escrowAddress, - chain_id: chainId, - reason, - }); - }; - - return ( - - - {t('worker.reportAbuse.modalHeader')} - - {isIdleOrLoading && ( - <> - - {t('worker.reportAbuse.modalParagraph')} - - - { - setReason(e.target.value); - }} - > - {REASON_OPTIONS.map((value) => ( - - {t(`worker.reportAbuse.reasons.${value}`)} - - ))} - - - - )} - {isPending && } - {isError && } - {isSuccess && } - - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/reward-amount.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/reward-amount.tsx index bf1861dc0c..ddfde6c4fb 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/reward-amount.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/reward-amount.tsx @@ -1,5 +1,6 @@ -import Tooltip from '@mui/material/Tooltip'; -import Typography from '@mui/material/Typography'; +import { Tooltip, Typography } from '@mui/material'; + +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; export function RewardAmount({ reward_amount, @@ -10,30 +11,49 @@ export function RewardAmount({ reward_token?: string; color?: string; }) { + const isMobile = useIsMobile(); + const variant = isMobile ? 'body2' : 'body1'; + if (!(reward_amount !== undefined && reward_token)) { return ''; } + const parsedReward = Number(reward_amount); const isNumeric = Number.isFinite(parsedReward); + if (!isNumeric) { return ( - + {`${reward_amount} ${reward_token}`} ); } + const hasDecimals = parsedReward - Math.floor(parsedReward) !== 0; if (hasDecimals) { return ( - + {`${parsedReward.toFixed(2)} ${reward_token}`} ); } + return ( - + {`${reward_amount} ${reward_token}`} ); diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/sorting.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/sorting.tsx deleted file mode 100644 index 037d5d0042..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/sorting.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { t } from 'i18next'; -import Typography from '@mui/material/Typography'; -import Box from '@mui/material/Box'; -import ListItemButton from '@mui/material/ListItemButton'; -import List from '@mui/material/List'; -import { Grid } from '@mui/material'; -import { SortArrow } from '@/shared/components/ui/icons'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { Button } from '@/shared/components/ui/button'; - -interface SortingProps { - fromHighestSelected: boolean; - fromLowestSelected: boolean; - sortFromHighest: () => void; - sortFromLowest: () => void; - clear: () => void; - label: React.ReactElement; -} - -export function Sorting({ - fromHighestSelected, - fromLowestSelected, - sortFromHighest, - sortFromLowest, - clear, - label, -}: SortingProps) { - const { colorPalette } = useColorMode(); - - return ( - <> - {label} - - - - - {t('worker.jobs.sortDirection.fromHighest')} - - - - - - - - From lowest - - - - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/index.ts index b977266dbe..8cb316adfb 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/index.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/index.ts @@ -1,6 +1,4 @@ -export * from './use-get-all-networks'; export * from './use-jobs-notifications'; -export * from './use-job-types-oracles-table'; export * from './use-jobs-filter-store'; export * from './use-my-jobs-filter-store'; export * from './use-get-my-jobs-data'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-all-networks.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-all-networks.ts deleted file mode 100644 index 61daf258dd..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-all-networks.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { useMemo } from 'react'; -import { getEnabledChainsByUiConfig } from '@/modules/smart-contracts/chains'; - -export const useGetAllNetworks = (chainIdsEnabled: number[]) => { - const allNetworks = useMemo(() => { - const chains = getEnabledChainsByUiConfig(chainIdsEnabled); - - return chains.map(({ chainId, name }) => ({ - option: chainId, - name, - })); - }, [chainIdsEnabled]); - - return { allNetworks }; -}; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-my-jobs-data.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-my-jobs-data.ts index 2576e22e10..d055d59693 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-my-jobs-data.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-my-jobs-data.ts @@ -1,48 +1,38 @@ import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; -import { useParams } from 'react-router-dom'; + import * as jobsService from '../services/jobs.service'; import { type MyJobPaginationResponse } from '../schemas'; -import { - useMyJobsFilterStore, - type MyJobsFilterStoreProps, -} from './use-my-jobs-filter-store'; - -type OracleParams = MyJobsFilterStoreProps['filterParams'] & { - oracle_address: string; -}; +import { useMyJobsFilterStore } from './use-my-jobs-filter-store'; export function useGetMyJobsData() { const { filterParams } = useMyJobsFilterStore(); - const { address } = useParams<{ address: string }>(); - const queryParams: OracleParams = { - ...filterParams, - oracle_address: address ?? '', - }; + const queryParams = { ...filterParams }; return useQuery({ queryKey: ['fetchMyJobs', queryParams], queryFn: async ({ signal }) => - jobsService.fetchMyJobs({ queryParams, signal }), + jobsService.fetchMyJobs({ queryParams: queryParams, signal }), + enabled: !!filterParams.oracle_address, }); } export function useInfiniteGetMyJobsData() { const { filterParams } = useMyJobsFilterStore(); - const { address } = useParams<{ address: string }>(); - const queryParams: OracleParams = { - ...filterParams, - oracle_address: address ?? '', - }; + const { page: _page, ...queryParams } = filterParams; return useInfiniteQuery({ initialPageParam: 0, queryKey: ['myJobsInfinite', queryParams], - queryFn: async ({ signal }) => - jobsService.fetchMyJobs({ queryParams, signal }), + queryFn: async ({ pageParam, signal }) => + jobsService.fetchMyJobs({ + queryParams: { ...queryParams, page: pageParam }, + signal, + }), + enabled: !!filterParams.oracle_address, getNextPageParam: (pageParams: MyJobPaginationResponse) => { return pageParams.total_pages - 1 <= pageParams.page ? undefined - : pageParams.page; + : pageParams.page + 1; }, }); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-job-types-oracles-table.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-job-types-oracles-table.tsx deleted file mode 100644 index 7dadc09e9f..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-job-types-oracles-table.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import { create } from 'zustand'; - -export interface JobsTypesOraclesFilterStore { - selectedJobTypes: string[]; - selectJobType: (jobType: string[]) => void; -} - -export const useJobsTypesOraclesFilterStore = - create((set) => ({ - selectedJobTypes: [], - selectJobType: (jobTypes: string[]) => { - set((state) => ({ - ...state, - selectedJobTypes: jobTypes, - })); - }, - })); diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-filter-store.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-filter-store.tsx index 395376680a..43cba5e2f2 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-filter-store.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-filter-store.tsx @@ -1,40 +1,22 @@ import { create } from 'zustand'; import type { PageSize } from '@/shared/types/entity.type'; -import { MyJobStatus, type SortDirection, type SortField } from '../types'; export interface JobsFilterStoreProps { filterParams: { - sort?: SortDirection; - sort_field?: SortField; - network?: 'MATIC' | 'POLYGON'; - // TODO add allowed job types - job_type?: string; - status?: MyJobStatus; - escrow_address?: string; page: number; page_size: PageSize; - fields: string[]; - chain_id?: number; }; setFilterParams: ( partialParams: Partial ) => void; resetFilterParams: () => void; - setSearchEscrowAddress: (escrow_address: string) => void; - setOracleAddress: (oracleAddress: string) => void; setPageParams: (pageIndex: number, pageSize: PageSize) => void; } const initialFiltersState = { page: 0, page_size: 5, - fields: ['reward_amount', 'job_description', 'reward_token'], - status: MyJobStatus.ACTIVE, - escrow_address: '', -} satisfies Pick< - JobsFilterStoreProps['filterParams'], - 'page_size' | 'page' | 'fields' | 'status' | 'escrow_address' ->; +} satisfies JobsFilterStoreProps['filterParams']; export const useJobsFilterStore = create((set) => ({ filterParams: initialFiltersState, @@ -63,22 +45,4 @@ export const useJobsFilterStore = create((set) => ({ resetFilterParams: () => { set({ filterParams: initialFiltersState }); }, - setSearchEscrowAddress: (escrow_address: string) => { - set((state) => ({ - ...state, - filterParams: { - ...state.filterParams, - escrow_address, - }, - })); - }, - setOracleAddress: (oracleAddress: string) => { - set((state) => ({ - ...state, - filterParams: { - ...state.filterParams, - oracle_address: oracleAddress, - }, - })); - }, })); diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-notifications.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-notifications.tsx index 474a8294f8..97322d8d96 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-notifications.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-notifications.tsx @@ -10,7 +10,7 @@ export const useJobsNotifications = () => { const onJobAssignmentSuccess = () => { showNotification({ - message: t('worker.jobs.successFullyAssignedJob'), + message: t('worker.jobs.successfullyAssignedJob'), type: TopNotificationType.SUCCESS, durationMs: 5000, }); @@ -19,7 +19,7 @@ export const useJobsNotifications = () => { const onJobAssignmentError = (error: Error) => { showNotification({ message: getErrorMessageForError(error), - type: TopNotificationType.WARNING, + type: TopNotificationType.ERROR, durationMs: 5000, }); }; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-my-jobs-filter-store.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-my-jobs-filter-store.tsx index 8138fd4027..1d8e403c10 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-my-jobs-filter-store.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-my-jobs-filter-store.tsx @@ -1,31 +1,24 @@ import { create } from 'zustand'; import type { PageSize } from '@/shared/types/entity.type'; -import { SortDirection, SortField, type MyJobStatus } from '../types'; +import { SortDirection, SortField, type StatusFilterType } from '../types'; export interface MyJobsFilterStoreProps { filterParams: { - sort?: SortDirection; - sort_field?: SortField; - job_type?: string; - status?: MyJobStatus; - escrow_address?: string; + status?: StatusFilterType; + oracle_address?: string; page: number; page_size: PageSize; - chain_id?: number; }; - availableJobTypes: string[]; setFilterParams: ( partialParams: Partial ) => void; resetFilterParams: () => void; - setSearchEscrowAddress: (escrow_address: string) => void; - setOracleAddress: (oracleAddress: string) => void; - setAvailableJobTypes: (jobTypes: string[]) => void; setPageParams: (pageIndex: number, pageSize: PageSize) => void; } const initialFiltersState = { - escrow_address: '', + oracle_address: undefined, + status: '', page: 0, page_size: 5, sort_field: SortField.CREATED_AT, @@ -34,7 +27,6 @@ const initialFiltersState = { export const useMyJobsFilterStore = create((set) => ({ filterParams: initialFiltersState, - availableJobTypes: [], setFilterParams: ( partialParams: Partial ) => { @@ -48,40 +40,25 @@ export const useMyJobsFilterStore = create((set) => ({ })); }, setPageParams: (pageIndex: number, pageSize: PageSize) => { - set((state) => ({ - ...state, - filterParams: { - ...state.filterParams, - page: pageIndex, - page_size: pageSize, - }, - })); + set((state) => { + if ( + state.filterParams.page === pageIndex && + state.filterParams.page_size === pageSize + ) { + return state; + } + + return { + ...state, + filterParams: { + ...state.filterParams, + page: pageIndex, + page_size: pageSize, + }, + }; + }); }, resetFilterParams: () => { set({ filterParams: initialFiltersState }); }, - setSearchEscrowAddress: (escrow_address: string) => { - set((state) => ({ - ...state, - filterParams: { - ...state.filterParams, - escrow_address, - }, - })); - }, - setOracleAddress: (oracleAddress: string) => { - set((state) => ({ - ...state, - filterParams: { - ...state.filterParams, - address: oracleAddress, - }, - })); - }, - setAvailableJobTypes: (jobTypes: string[]) => { - set((state) => ({ - ...state, - jobTypes, - })); - }, })); diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/index.ts deleted file mode 100644 index 846093be12..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './jobs.page'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/jobs.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/jobs.page.tsx deleted file mode 100644 index 21364ccedf..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/jobs.page.tsx +++ /dev/null @@ -1,138 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Box, Grid, Paper, Stack, Tab, Tabs, Typography } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { useParams } from 'react-router-dom'; -import { TableQueryContextProvider } from '@/shared/components/ui/table/table-query-context'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { NoRecords } from '@/shared/components/ui/no-records'; -import { PageCardLoader } from '@/shared/components/ui/page-card'; -import { useUiConfig } from '@/shared/providers/ui-config-provider'; -import { useGetOracles } from '../hooks'; -import { useGetOraclesNotifications } from '../hooks/use-get-oracles-notifications'; -import { TabPanel } from './components'; -import { AvailableJobsView } from './available-jobs'; -import { MyJobsView } from './my-jobs/my-jobs-view'; - -function generateTabA11yProps(index: number) { - return { - id: `tab-${index.toString()}`, - 'aria-controls': `jobs-tabpanel-${index.toString()}`, - }; -} - -export function JobsPage() { - const { isDarkMode } = useColorMode(); - const { - data, - isError: isErrorGetOracles, - isPending: isPendingGetOracles, - error, - } = useGetOracles(); - - const { uiConfig, isUiConfigLoading, isUiConfigError } = useUiConfig(); - - const { address: oracle_address } = useParams<{ address: string }>(); - const { t } = useTranslation(); - const [activeTab, setActiveTab] = useState(0); - const isMobile = useIsMobile(); - - const isError = isErrorGetOracles || isUiConfigError; - const isPending = isPendingGetOracles || isUiConfigLoading; - const { onError } = useGetOraclesNotifications(); - - const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => { - setActiveTab(newValue); - }; - - useEffect(() => { - if (error) { - onError(error); - } - }, [error, onError]); - - const oracleName = data?.find( - ({ address }) => address === oracle_address - )?.name; - - if (isPending) { - return ; - } - - return ( - - - - {!isError && ( - - {oracleName} - - )} - - - - - - - - - - - {isError ? ( - - ) : ( - - )} - - - {isError ? ( - - ) : ( - - )} - - - - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/columns.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/columns.tsx deleted file mode 100644 index 5e4594d32d..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/columns.tsx +++ /dev/null @@ -1,175 +0,0 @@ -import { t } from 'i18next'; -import Grid from '@mui/material/Grid'; -import { type MRT_ColumnDef } from 'material-react-table'; -import RefreshIcon from '@mui/icons-material/Refresh'; -import { TableHeaderCell } from '@/shared/components/ui/table/table-header-cell'; -import { getNetworkName } from '@/modules/smart-contracts/get-network-name'; -import { Button } from '@/shared/components/ui/button'; -import { Chip } from '@/shared/components/ui/chip'; -import type { JobType } from '@/modules/smart-contracts/EthKVStore/config'; -import { formatDate } from '@/shared/helpers/date'; -import { - EvmAddress, - RewardAmount, - MyJobsTableActions, -} from '../../../components'; -import { type MyJob } from '../../../schemas'; -import { StatusChip } from './status-chip'; -import { MyJobsExpiresAtSort } from './my-jobs-expires-at-sort'; -import { MyJobsJobTypeFilter } from './my-jobs-job-type-filter'; -import { MyJobsNetworkFilter } from './my-jobs-network-filter'; -import { MyJobsRewardAmountSort } from './my-jobs-reward-amount-sort'; -import { MyJobsStatusFilter } from './my-jobs-status-filter'; - -export const getColumnsDefinition = ({ - refreshData, - isRefreshTasksPending, - chainIdsEnabled, -}: { - refreshData: () => void; - isRefreshTasksPending: boolean; - chainIdsEnabled: number[]; -}): MRT_ColumnDef[] => [ - { - accessorKey: 'escrow_address', - header: t('worker.jobs.escrowAddress'), - size: 100, - enableSorting: true, - Cell: (props) => { - return ; - }, - }, - { - accessorKey: 'network', - header: t('worker.jobs.network'), - size: 100, - Cell: (props) => { - return getNetworkName(props.row.original.chain_id); - }, - Header: ( - - } - /> - ), - }, - { - accessorKey: 'reward_amount', - header: t('worker.jobs.rewardAmount'), - size: 100, - enableSorting: true, - Cell: (props) => { - const { reward_amount, reward_token } = props.row.original; - return ( - - ); - }, - Header: ( - } - /> - ), - }, - { - accessorKey: 'job_type', - header: t('worker.jobs.jobType'), - size: 100, - enableSorting: true, - Cell: ({ row }) => { - const label = t(`jobTypeLabels.${row.original.job_type as JobType}`); - return ; - }, - Header: ( - } - /> - ), - }, - { - accessorKey: 'expires_at', - header: t('worker.jobs.expiresAt'), - size: 100, - enableSorting: true, - Cell: (props) => { - return formatDate(props.row.original.expires_at); - }, - Header: ( - } - /> - ), - }, - { - accessorKey: 'status', - header: t('worker.jobs.status'), - size: 100, - enableSorting: true, - Cell: (props) => { - const status = props.row.original.status; - return ; - }, - Header: ( - } - /> - ), - }, - { - accessorKey: 'assignment_id', - header: t('worker.jobs.refresh'), - size: 100, - enableSorting: true, - Cell: (props) => ( - - - - ), - Header: ( - - - - ), - }, -]; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/index.ts deleted file mode 100644 index 677305df30..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './my-jobs-table'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-expires-at-sort.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-expires-at-sort.tsx deleted file mode 100644 index 608edc3452..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-expires-at-sort.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { t } from 'i18next'; -import { Sorting } from '@/shared/components/ui/table/table-header-menu/sorting'; -import { useMyJobsFilterStore } from '../../../hooks'; -import { SortDirection, SortField } from '../../../types'; - -export function MyJobsExpiresAtSort() { - const { setFilterParams } = useMyJobsFilterStore(); - - const sortAscExpiresAt = () => { - setFilterParams({ - sort_field: SortField.EXPIRES_AT, - sort: SortDirection.ASC, - }); - }; - - const sortDescExpiresAt = () => { - setFilterParams({ - sort_field: SortField.EXPIRES_AT, - sort: SortDirection.DESC, - }); - }; - - return ( - { - setFilterParams({ - sort_field: undefined, - sort: undefined, - }); - }} - sortingOptions={[ - { - label: t('worker.jobs.sortDirection.closestToNow'), - sortCallback: sortAscExpiresAt, - }, - { - label: t('worker.jobs.sortDirection.furthestToNow'), - sortCallback: sortDescExpiresAt, - }, - ]} - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-filters.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-filters.tsx new file mode 100644 index 0000000000..0fe0bc6fdf --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-filters.tsx @@ -0,0 +1,209 @@ +import { useEffect, useState, type SubmitEvent } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + Box, + FormControlLabel, + IconButton, + Radio, + RadioGroup, + Stack, + Typography, +} from '@mui/material'; + +import { FilterIcon } from '@/shared/components/ui/icons'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { ResponsiveOverlay } from '@/shared/components/ui/responsive-overlay'; +import { useGetOracles } from '@/modules/worker/hooks/use-get-oracles'; +import { Alert } from '@/shared/components/ui/alert'; +import { Button } from '@/shared/components/ui/button'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { useMyJobsFilterStore } from '../../../hooks'; +import { type StatusFilterType } from '../../../types'; +import { STATUS_FILTER_OPTIONS } from './status-filter'; + +const CVAT_ORACLE_NAME = 'cvat'; + +const getDefaultOracleAddress = ( + oracles: { address: string; name: string }[] | undefined +) => { + if (!oracles?.length) { + return undefined; + } + + return ( + oracles.find((oracle) => + oracle.name.toLowerCase().includes(CVAT_ORACLE_NAME) + )?.address ?? oracles[0].address + ); +}; + +export function MyJobsFilters() { + const [isOpen, setIsOpen] = useState(false); + const [draftOracleAddress, setDraftOracleAddress] = useState(''); + const [draftStatus, setDraftStatus] = useState(''); + + const { colorPalette } = useColorMode(); + const { t } = useTranslation(); + const isMobile = useIsMobile(); + const { filterParams, setFilterParams } = useMyJobsFilterStore(); + + const { data: oraclesData, isError, isPending } = useGetOracles(); + + const defaultOracleAddress = getDefaultOracleAddress(oraclesData); + const selectedOracleAddress = + filterParams.oracle_address ?? defaultOracleAddress ?? ''; + const selectedStatus = filterParams.status ?? ''; + + useEffect(() => { + if (!filterParams.oracle_address && defaultOracleAddress) { + setFilterParams({ oracle_address: defaultOracleAddress }); + } + }, [defaultOracleAddress, filterParams.oracle_address, setFilterParams]); + + useEffect(() => { + if (isOpen) { + setDraftOracleAddress(selectedOracleAddress); + setDraftStatus(selectedStatus); + } + }, [isOpen, selectedOracleAddress, selectedStatus]); + + const handleSubmit = (e: SubmitEvent) => { + e.preventDefault(); + setFilterParams({ + oracle_address: draftOracleAddress, + status: draftStatus, + }); + setIsOpen(false); + }; + + return ( + <> + setIsOpen(!isOpen)} + > + + + + setIsOpen(false)} + desktopSx={{ p: 0 }} + mobileSx={{ p: 0 }} + > + + + + {t('worker.jobs.jobsFilter')} + + + + {t('worker.jobs.oracles')} + + {isError && ( + + {t('worker.oraclesTable.error.gettingOracles')} + + )} + { + setDraftOracleAddress(oracleAddress); + }} + > + {(oraclesData ?? []).map((oracle) => ( + } + label={ + + {oracle.name} + + } + value={oracle.address} + /> + ))} + + + {isMobile && ( + + + {t('worker.jobs.status')} + + { + setDraftStatus(status as StatusFilterType); + }} + > + {STATUS_FILTER_OPTIONS.map((option) => ( + } + label={ + + {t(option.labelKey)} + + } + value={option.value} + /> + ))} + + + )} + + + + + + + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-job-type-filter.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-job-type-filter.tsx deleted file mode 100644 index 7b9fd2e428..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-job-type-filter.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { useMemo } from 'react'; -import { Filtering } from '@/shared/components/ui/table/table-header-menu/filtering'; -import { JOB_TYPES } from '@/shared/consts'; -import { useMyJobsFilterStore } from '../../../hooks'; - -export function MyJobsJobTypeFilter() { - const { t } = useTranslation(); - const { setFilterParams, filterParams } = useMyJobsFilterStore(); - const filteringOptions = useMemo( - () => - JOB_TYPES.map((jobType) => ({ - name: t(`jobTypeLabels.${jobType}`), - option: jobType, - })), - [t] - ); - return ( - { - setFilterParams({ - job_type: undefined, - }); - }} - filteringOptions={filteringOptions} - isChecked={(option) => option === filterParams.job_type} - setFiltering={(jobType) => { - setFilterParams({ - job_type: jobType, - }); - }} - showClearButton - showTitle - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-network-filter.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-network-filter.tsx deleted file mode 100644 index b404ffada7..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-network-filter.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Filtering } from '@/shared/components/ui/table/table-header-menu/filtering'; -import { useMyJobsFilterStore, useGetAllNetworks } from '../../../hooks'; - -interface MyJobsNetworkFilterProps { - chainIdsEnabled: number[]; -} - -export function MyJobsNetworkFilter({ - chainIdsEnabled, -}: Readonly) { - const { setFilterParams, filterParams } = useMyJobsFilterStore(); - const { allNetworks } = useGetAllNetworks(chainIdsEnabled); - - return ( - { - setFilterParams({ - chain_id: undefined, - }); - }} - filteringOptions={allNetworks} - isChecked={(option) => option === filterParams.chain_id} - setFiltering={(chainId) => { - setFilterParams({ - chain_id: chainId, - }); - }} - showClearButton - showTitle - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-reward-amount-sort.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-reward-amount-sort.tsx deleted file mode 100644 index 2e1a335540..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-reward-amount-sort.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { t } from 'i18next'; -import { Sorting } from '@/shared/components/ui/table/table-header-menu/sorting'; -import { useMyJobsFilterStore } from '../../../hooks'; -import { SortDirection, SortField } from '../../../types'; - -export function MyJobsRewardAmountSort() { - const { setFilterParams } = useMyJobsFilterStore(); - - const sortAscRewardAmount = () => { - setFilterParams({ - sort_field: SortField.REWARD_AMOUNT, - sort: SortDirection.ASC, - }); - }; - - const sortDescRewardAmount = () => { - setFilterParams({ - sort_field: SortField.REWARD_AMOUNT, - sort: SortDirection.DESC, - }); - }; - - return ( - { - setFilterParams({ - sort_field: undefined, - sort: undefined, - }); - }} - sortingOptions={[ - { - label: t('worker.jobs.sortDirection.fromHighest'), - sortCallback: sortDescRewardAmount, - }, - { - label: t('worker.jobs.sortDirection.fromLowest'), - sortCallback: sortAscRewardAmount, - }, - ]} - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-status-filter.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-status-filter.tsx deleted file mode 100644 index 4c17f3f157..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-status-filter.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import capitalize from 'lodash/capitalize'; -import { Filtering } from '@/shared/components/ui/table/table-header-menu/filtering'; -import { useMyJobsFilterStore } from '../../../hooks'; -import { MyJobStatus } from '../../../types'; - -export function MyJobsStatusFilter() { - const { setFilterParams, filterParams } = useMyJobsFilterStore(); - - return ( - { - setFilterParams({ - status: undefined, - }); - }} - filteringOptions={Object.values(MyJobStatus).map((status) => ({ - name: capitalize(status), - option: status, - }))} - isChecked={(status) => status === filterParams.status} - setFiltering={(status) => { - setFilterParams({ - status, - }); - }} - showClearButton - showTitle - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-table.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-table.tsx index 0fd5c61c70..93994aaa73 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-table.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-table.tsx @@ -1,126 +1,145 @@ -import { t } from 'i18next'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useParams } from 'react-router-dom'; +import { useEffect, useMemo, useState } from 'react'; import { MaterialReactTable, + type MRT_PaginationState, useMaterialReactTable, } from 'material-react-table'; + import { useColorMode } from '@/shared/contexts/color-mode'; import { createTableDarkMode } from '@/shared/styles/create-table-dark-mode'; -import { EscrowAddressSearchForm } from '../../../components'; import { useGetMyJobsData, useMyJobsFilterStore } from '../../../hooks'; -import { useRefreshJobsMutation } from '../../hooks'; -import { getColumnsDefinition } from './columns'; - -interface MyJobsTableProps { - chainIdsEnabled: number[]; -} +import { useGetMyJobsColumns } from '../../hooks/use-get-my-jobs-columns'; -export function MyJobsTable({ chainIdsEnabled }: Readonly) { +export function MyJobsTable() { const { colorPalette, isDarkMode } = useColorMode(); - const { - setSearchEscrowAddress, - setPageParams, - filterParams, - resetFilterParams, - } = useMyJobsFilterStore(); - const { data: tableData, status: tableStatus } = useGetMyJobsData(); + const { data: tableData, isPending, isError } = useGetMyJobsData(); + const { setPageParams, filterParams } = useMyJobsFilterStore(); + const [paginationState, setPaginationState] = useState( + () => ({ + pageIndex: filterParams.page, + pageSize: filterParams.page_size, + }) + ); const memoizedTableDataResults = useMemo( () => tableData?.results ?? [], [tableData?.results] ); - const { mutate: refreshTasksMutation, isPending: isRefreshTasksPending } = - useRefreshJobsMutation(); - const { address: oracle_address } = useParams<{ address: string }>(); - - const [paginationState, setPaginationState] = useState({ - pageIndex: 0, - pageSize: 5, - }); - - const refreshData = useCallback(() => { - refreshTasksMutation({ oracle_address: oracle_address ?? '' }); - }, [refreshTasksMutation, oracle_address]); + const columns = useGetMyJobsColumns(); useEffect(() => { if (paginationState.pageSize === 5 || paginationState.pageSize === 10) { setPageParams(paginationState.pageIndex, paginationState.pageSize); } - }, [paginationState, setPageParams]); + }, [paginationState.pageIndex, paginationState.pageSize, setPageParams]); useEffect(() => { - setPaginationState({ - pageIndex: filterParams.page, - pageSize: filterParams.page_size, + setPaginationState((currentPaginationState) => { + if ( + currentPaginationState.pageIndex === filterParams.page && + currentPaginationState.pageSize === filterParams.page_size + ) { + return currentPaginationState; + } + + return { + pageIndex: filterParams.page, + pageSize: filterParams.page_size, + }; }); }, [filterParams.page, filterParams.page_size]); - useEffect(() => { - return () => { - resetFilterParams(); - }; - }, [resetFilterParams]); - const table = useMaterialReactTable({ - columns: getColumnsDefinition({ - refreshData, - isRefreshTasksPending, - chainIdsEnabled, - }), + columns, data: memoizedTableDataResults, state: { - isLoading: tableStatus === 'pending', - showAlertBanner: tableStatus === 'error', + isLoading: isPending, + showAlertBanner: isError, pagination: paginationState, }, - enablePagination: Boolean(tableData?.total_pages), + enablePagination: !!tableData?.total_pages, manualPagination: true, - onPaginationChange: (updater) => { - setPaginationState(updater); - }, + onPaginationChange: setPaginationState, muiPaginationProps: { + rowsPerPageOptions: [5, 10], SelectProps: { sx: { + '.MuiSelect-select': { + color: colorPalette.text.auxiliary100, + }, '.MuiSelect-icon': { + transition: 'none', ':hover': { backgroundColor: 'blue', }, - fill: colorPalette.text.primary, + fill: colorPalette.text.auxiliary100, }, }, }, - rowsPerPageOptions: [5, 10], + }, + muiBottomToolbarProps: { + sx: { + bgcolor: colorPalette.background.paper, + boxShadow: 'none', + color: colorPalette.text.auxiliary100, + transition: 'none', + }, }, pageCount: tableData?.total_pages ?? -1, rowCount: tableData?.total_results, enableColumnActions: false, enableColumnFilters: false, enableSorting: false, - renderTopToolbar: () => ( - { - setSearchEscrowAddress(address); - }} - /> - ), + renderTopToolbar: false, + muiTablePaperProps: { + sx: { + boxShadow: 'none', + transition: 'none', + }, + }, + muiTableHeadProps: { + sx: { + backgroundColor: colorPalette.background.default, + }, + }, + muiTableHeadRowProps: { + sx: { + backgroundColor: 'inherit', + boxShadow: 'none', + }, + }, muiTableHeadCellProps: { sx: { - borderColor: colorPalette.paper.text, + paddingTop: '8px', + paddingBottom: '8px', + paddingLeft: '12px', + paddingRight: '12px', + borderColor: colorPalette.background.paper, + color: colorPalette.text.auxiliary200, + typography: 'body1', + fontWeight: 500, + '& .Mui-TableHeadCell-Content': { + justifyContent: 'center', + textAlign: 'center', + }, }, }, muiTableBodyCellProps: { + align: 'center', sx: { - borderColor: colorPalette.paper.text, + textAlign: 'center', + borderBottom: `1px solid ${colorPalette.border.main}`, }, }, - muiTablePaperProps: { + muiTableBodyRowProps: { sx: { - boxShadow: '0px 2px 2px 0px #E9EBFA80', + bgcolor: colorPalette.background.paper, + borderBottom: `1px solid ${colorPalette.border.main}`, + transition: 'none', + '&:last-of-type': { + borderBottom: 'none', + }, }, }, ...(isDarkMode ? createTableDarkMode(colorPalette) : {}), diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/status-chip.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/status-chip.tsx deleted file mode 100644 index 55172fa9f0..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/status-chip.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import Box from '@mui/material/Box'; -import Typography from '@mui/material/Typography'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { getChipStatusColor } from '../../utils'; -import { type MyJob } from '../../../schemas'; - -export function StatusChip({ status }: Readonly<{ status: MyJob['status'] }>) { - const { colorPalette } = useColorMode(); - - return ( - - - {status} - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/status-filter.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/status-filter.tsx new file mode 100644 index 0000000000..cd3fcc93c7 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/status-filter.tsx @@ -0,0 +1,73 @@ +import { useTranslation } from 'react-i18next'; +import { Button } from '@/shared/components/ui/button'; +import { Stack } from '@mui/material'; +import { MyJobStatus, type StatusFilterType } from '../../../types'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { useMyJobsFilterStore } from '../../../hooks'; + +export const STATUS_FILTER_OPTIONS = [ + { + labelKey: 'worker.jobs.statusFilter.all', + value: '', + }, + { + labelKey: 'worker.jobs.statusFilter.inProgress', + value: MyJobStatus.ACTIVE, + }, + { + labelKey: 'worker.jobs.statusFilter.completed', + value: MyJobStatus.COMPLETED, + }, +] as const satisfies { + labelKey: string; + value: StatusFilterType; +}[]; + +export function StatusFilter() { + const { t } = useTranslation(); + const { colorPalette } = useColorMode(); + const { filterParams, setFilterParams } = useMyJobsFilterStore(); + const activeStatus = filterParams.status ?? ''; + + return ( + + {STATUS_FILTER_OPTIONS.map((option) => { + const isActive = option.value === activeStatus; + return ( + + ); + })} + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/index.ts deleted file mode 100644 index cb17920d1b..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './my-jobs-filter-modal'; -export * from './my-jobs-list-mobile'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-card-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-card-mobile.tsx new file mode 100644 index 0000000000..7200764be8 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-card-mobile.tsx @@ -0,0 +1,163 @@ +import { Chip, Paper, Stack, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; + +import { + EvmAddress, + MyJobsTableActions, + RewardAmount, +} from '../../../components'; +import { type MyJob } from '../../../schemas'; +import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode'; +import { JobType } from '@/shared/types/entity.type'; +import { TimeUntil } from '../time-until'; +import { ChainIcon } from '@/shared/components/ui/chain-icon'; +import { + JobExpiryTimeIcon, + JobStatusIcon, + OracleAddressIcon, + OracleRewardIcon, +} from '@/shared/components/ui/icons'; + +const Row = ({ + label, + icon, + children, +}: { + label: string; + icon: React.ReactNode; + children: React.ReactNode; +}) => { + const { colorPalette } = useColorMode(); + return ( + + {icon} + + {label}:{' '} + + {children} + + ); +}; + +export function MyJobsCardMobile({ job }: { job: MyJob }) { + const { colorPalette } = useColorMode(); + const { t } = useTranslation(); + + const jobTypeLabel = t(`jobTypeLabels.${job.job_type as JobType}`); + + const hasUrl = !!job.url; + + return ( + + + + + + + + } + > + + + + } + > + + + + + } + > + + {job.status.toLowerCase()} + + + + } + > + + + + {hasUrl && ( + + + + )} + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-expires-at-sort-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-expires-at-sort-mobile.tsx deleted file mode 100644 index 9b186669f9..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-expires-at-sort-mobile.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import Typography from '@mui/material/Typography'; -import { t } from 'i18next'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { Sorting } from '../../../components'; -import { useMyJobsFilterStore } from '../../../hooks'; -import { SortDirection, SortField } from '../../../types'; - -export function MyJobsExpiresAtSortMobile() { - const { setFilterParams, filterParams } = useMyJobsFilterStore(); - const { colorPalette } = useColorMode(); - - return ( - - {t('worker.jobs.expiresAt')} - - } - fromHighestSelected={ - filterParams.sort_field === SortField.EXPIRES_AT && - filterParams.sort === SortDirection.DESC - } - sortFromHighest={() => { - setFilterParams({ - sort: SortDirection.DESC, - sort_field: SortField.EXPIRES_AT, - }); - }} - fromLowestSelected={ - filterParams.sort_field === SortField.EXPIRES_AT && - filterParams.sort === SortDirection.ASC - } - sortFromLowest={() => { - setFilterParams({ - sort: SortDirection.ASC, - sort_field: SortField.EXPIRES_AT, - }); - }} - clear={() => { - setFilterParams({ - sort: undefined, - sort_field: undefined, - }); - }} - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-filter-modal.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-filter-modal.tsx deleted file mode 100644 index 291f36a6eb..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-filter-modal.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import Box from '@mui/material/Box'; -import Drawer from '@mui/material/Drawer'; -import CssBaseline from '@mui/material/CssBaseline'; -import { Divider, IconButton, Stack, Typography } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import CloseIcon from '@mui/icons-material/Close'; -import { HumanLogoIcon } from '@/shared/components/ui/icons'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { useHandleMainNavIconClick } from '@/shared/hooks/use-handle-main-nav-icon-click'; -import { MyJobsNetworkFilterMobile } from './my-jobs-network-filter-mobile'; -import { MyJobsJobTypeFilterMobile } from './my-jobs-job-type-filter-mobile'; -import { MyJobsStatusFilterMobile } from './my-jobs-status-filter-mobile'; -import { MyJobsExpiresAtSortMobile } from './my-jobs-expires-at-sort-mobile'; -import { MyJobsRewardAmountSortMobile } from './my-jobs-reward-amount-sort-mobile'; - -interface MyJobsFilterModalProps { - chainIdsEnabled: number[]; - close: () => void; -} -export function MyJobsFilterModal({ - chainIdsEnabled, - close, -}: Readonly) { - const handleMainNavIconClick = useHandleMainNavIconClick(); - const { colorPalette } = useColorMode(); - const { t } = useTranslation(); - - return ( - - - - - { - handleMainNavIconClick(); - }} - > - - - - - - - - - {t('worker.jobs.mobileFilterDrawer.filters')} - - - - {t('worker.jobs.mobileFilterDrawer.sortBy')} - - - - - {t('worker.jobs.mobileFilterDrawer.filters')} - - - - {t('worker.jobs.network')} - - - - - - - - {t('worker.jobs.jobType')} - - - - - - - {t('worker.jobs.status')} - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-job-type-filter-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-job-type-filter-mobile.tsx deleted file mode 100644 index 7e9ba0eb96..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-job-type-filter-mobile.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { useTranslation } from 'react-i18next'; -import { Filtering } from '@/shared/components/ui/table/table-header-menu/filtering'; -import { JOB_TYPES } from '@/shared/consts'; -import { useMyJobsFilterStore } from '../../../hooks'; - -export function MyJobsJobTypeFilterMobile() { - const { t } = useTranslation(); - const { setFilterParams, filterParams } = useMyJobsFilterStore(); - - return ( - { - setFilterParams({ - job_type: undefined, - page: 0, - }); - }} - filteringOptions={JOB_TYPES.map((jobType) => ({ - name: t(`jobTypeLabels.${jobType}`), - option: jobType, - }))} - isChecked={(option) => option === filterParams.job_type} - setFiltering={(jobType) => { - setFilterParams({ - job_type: jobType, - page: 0, - }); - }} - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-list-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-list-mobile.tsx index bc03195ef0..a38d313de6 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-list-mobile.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-list-mobile.tsx @@ -1,208 +1,51 @@ -import { Grid, List, Paper, Stack, Typography } from '@mui/material'; +import { Stack } from '@mui/material'; import { useTranslation } from 'react-i18next'; -import { useEffect } from 'react'; -import { useParams } from 'react-router-dom'; + import { Button } from '@/shared/components/ui/button'; -import { FiltersButtonIcon, RefreshIcon } from '@/shared/components/ui/icons'; import { Loader } from '@/shared/components/ui/loader'; import { Alert } from '@/shared/components/ui/alert'; -import { getNetworkName } from '@/modules/smart-contracts/get-network-name'; import { getErrorMessageForError } from '@/shared/errors'; -import { ListItem } from '@/shared/components/ui/list-item'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { Chip } from '@/shared/components/ui/chip'; -import type { JobType } from '@/modules/smart-contracts/EthKVStore/config'; -import { formatDate } from '@/shared/helpers/date'; import { useCombinePages } from '@/shared/hooks'; -import { - EscrowAddressSearchForm, - EvmAddress, - RewardAmount, - MyJobsTableActions, -} from '../../../components'; -import { - useMyJobsFilterStore, - useJobsFilterStore, - useInfiniteGetMyJobsData, -} from '../../../hooks'; -import { useRefreshJobsMutation } from '../../hooks'; -import { getChipStatusColor } from '../../utils'; +import { useMyJobsFilterStore, useInfiniteGetMyJobsData } from '../../../hooks'; import { type MyJob } from '../../../schemas'; -import { useMyJobFilterModal } from '../../hooks/use-my-jobs-filter-modal'; +import { MyJobsCardMobile } from './my-jobs-card-mobile'; export function MyJobsListMobile() { - const { colorPalette } = useColorMode(); - const { filterParams, setPageParams, resetFilterParams } = - useMyJobsFilterStore(); - const { t } = useTranslation(); - const { - data: tableData, - status: tableStatus, - isError: isTableError, - error: tableError, - fetchNextPage, - hasNextPage, - } = useInfiniteGetMyJobsData(); - - const { mutate: refreshTasksMutation, isPending: isRefreshTasksPending } = - useRefreshJobsMutation(); - const { setSearchEscrowAddress } = useJobsFilterStore(); - const { address: oracle_address } = useParams<{ address: string }>(); + const { filterParams, setPageParams } = useMyJobsFilterStore(); + const { data, isPending, isError, error, fetchNextPage, hasNextPage } = + useInfiniteGetMyJobsData(); - const allPages = useCombinePages(tableData, filterParams.page); - const { openModal } = useMyJobFilterModal(); - - useEffect(() => { - return () => { - resetFilterParams(); - }; - }, [resetFilterParams]); + const allPages = useCombinePages(data); return ( - <> - - - - - - - - - - - - {isTableError ? ( - - {getErrorMessageForError(tableError)} - - ) : null} - {tableStatus === 'pending' ? ( - - - - ) : null} - {allPages.map((d) => { - return ( - - - - - - - - - - {d.expires_at ? formatDate(d.expires_at) : ''} - - - - - - - - - - {getNetworkName(d.chain_id)} - - - - - {d.status} - - } - /> - - - - -
- - - -
- - - ); + + {isError && ( + + {getErrorMessageForError(error)} + + )} + {isPending && ( + + + + )} + {!isPending && + !isError && + allPages.map((d) => { + return ; })} - {hasNextPage ? ( - - ) : null} - - + {hasNextPage && ( + + )} + ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-network-filter-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-network-filter-mobile.tsx deleted file mode 100644 index 550df051f4..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-network-filter-mobile.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { Filtering } from '@/shared/components/ui/table/table-header-menu/filtering'; -import { useMyJobsFilterStore, useGetAllNetworks } from '../../../hooks'; - -interface MyJobsNetworkFilterMobileProps { - chainIdsEnabled: number[]; -} - -export function MyJobsNetworkFilterMobile({ - chainIdsEnabled, -}: Readonly) { - const { setFilterParams, filterParams } = useMyJobsFilterStore(); - const { allNetworks } = useGetAllNetworks(chainIdsEnabled); - - return ( - { - setFilterParams({ - chain_id: undefined, - page: 0, - }); - }} - filteringOptions={allNetworks} - isChecked={(option) => option === filterParams.chain_id} - setFiltering={(chainId) => { - setFilterParams({ - chain_id: chainId, - page: 0, - }); - }} - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-reward-amount-sort-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-reward-amount-sort-mobile.tsx deleted file mode 100644 index f517d1940f..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-reward-amount-sort-mobile.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import Typography from '@mui/material/Typography'; -import { t } from 'i18next'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { Sorting } from '../../../components'; -import { useMyJobsFilterStore } from '../../../hooks'; -import { SortDirection, SortField } from '../../../types'; - -export function MyJobsRewardAmountSortMobile() { - const { setFilterParams, filterParams } = useMyJobsFilterStore(); - const { colorPalette } = useColorMode(); - const isRewardAmountSortSelected = - filterParams.sort_field === SortField.REWARD_AMOUNT; - - return ( - - {t('worker.jobs.rewardAmount')} - - } - fromHighestSelected={ - isRewardAmountSortSelected && filterParams.sort === SortDirection.DESC - } - sortFromHighest={() => { - setFilterParams({ - sort: SortDirection.DESC, - sort_field: SortField.REWARD_AMOUNT, - }); - }} - fromLowestSelected={ - isRewardAmountSortSelected && filterParams.sort === SortDirection.ASC - } - sortFromLowest={() => { - setFilterParams({ - sort: SortDirection.ASC, - sort_field: SortField.REWARD_AMOUNT, - }); - }} - clear={() => { - setFilterParams({ - sort: undefined, - sort_field: undefined, - }); - }} - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-status-filter-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-status-filter-mobile.tsx deleted file mode 100644 index d1199971d8..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-status-filter-mobile.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import capitalize from 'lodash/capitalize'; -import { Filtering } from '@/shared/components/ui/table/table-header-menu/filtering'; -import { useMyJobsFilterStore } from '../../../hooks'; -import { MyJobStatus } from '../../../types'; - -export function MyJobsStatusFilterMobile() { - const { setFilterParams, filterParams } = useMyJobsFilterStore(); - - return ( - { - setFilterParams({ - status: undefined, - page: 0, - }); - }} - filteringOptions={Object.values(MyJobStatus).map((status) => ({ - name: capitalize(status), - option: status, - }))} - isChecked={(status) => status === filterParams.status} - setFiltering={(status) => { - setFilterParams({ - status, - page: 0, - }); - }} - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/time-until.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/time-until.tsx new file mode 100644 index 0000000000..d2edfe0aa9 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/time-until.tsx @@ -0,0 +1,71 @@ +import { useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { formatDistanceToNow } from 'date-fns'; +import { enUS } from 'date-fns/locale'; + +const SECOND_IN_MS = 1000; + +const DATE_FNS_LOCALES = { + en: enUS, +} as const; + +export function TimeUntil({ date }: { date?: string | null }) { + const { t, i18n } = useTranslation(); + const [now, setNow] = useState(Date.now()); + + const dateFnsLocale = useMemo(() => { + const language = i18n.resolvedLanguage ?? i18n.language; + const baseLanguage = language.split( + '-' + )[0] as keyof typeof DATE_FNS_LOCALES; + + return DATE_FNS_LOCALES[baseLanguage] ?? enUS; + }, [i18n.language, i18n.resolvedLanguage]); + + const targetDate = useMemo(() => { + if (!date) { + return null; + } + + const parsedDate = new Date(date); + + return Number.isNaN(parsedDate.getTime()) ? null : parsedDate; + }, [date]); + + useEffect(() => { + const targetTime = targetDate?.getTime(); + + if (targetTime == null || targetTime <= Date.now()) { + return; + } + + setNow(Date.now()); + + const intervalId = setInterval(() => { + const nextNow = Date.now(); + + setNow(nextNow); + + if (nextNow >= targetTime) { + clearInterval(intervalId); + } + }, SECOND_IN_MS); + + return () => { + clearInterval(intervalId); + }; + }, [targetDate]); + + if (targetDate === null || targetDate.getTime() <= now) { + return t('worker.jobs.expired'); + } + + return ( + <> + {formatDistanceToNow(targetDate, { + includeSeconds: true, + locale: dateFnsLocale, + })} + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-get-my-jobs-columns.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-get-my-jobs-columns.tsx new file mode 100644 index 0000000000..a5026826d3 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-get-my-jobs-columns.tsx @@ -0,0 +1,142 @@ +import { useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Chip, Grid, Typography } from '@mui/material'; +import { type MRT_ColumnDef } from 'material-react-table'; + +import type { JobType } from '@/shared/types/entity.type'; +import { EvmAddress, RewardAmount, MyJobsTableActions } from '../../components'; +import { type MyJob } from '../../schemas'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { ChainIcon } from '@/shared/components/ui/chain-icon'; +import { TimeUntil } from '../components/time-until'; + +const COL_SIZE_SM = 50; +const COL_SIZE = 100; +const COL_SIZE_MD = 150; + +export const useGetMyJobsColumns = (): MRT_ColumnDef[] => { + const { colorPalette } = useColorMode(); + const isMobile = useIsMobile(); + const { t } = useTranslation(); + + return useMemo( + () => [ + { + accessorKey: 'escrow_address', + header: t('worker.jobs.address'), + size: COL_SIZE, + enableSorting: true, + Cell: (props) => { + return ; + }, + }, + { + accessorKey: 'network', + header: t('worker.jobs.network'), + size: COL_SIZE_SM, + Cell: (props) => { + return ; + }, + }, + { + accessorKey: 'reward_amount', + header: t('worker.jobs.reward'), + size: COL_SIZE_MD, + enableSorting: true, + Cell: (props) => { + const { reward_amount, reward_token } = props.row.original; + return ( + + ); + }, + }, + { + accessorKey: 'job_type', + header: t('worker.jobs.jobType'), + size: COL_SIZE, + enableSorting: true, + Cell: ({ row }) => { + const label = t(`jobTypeLabels.${row.original.job_type as JobType}`); + return ( + + ); + }, + }, + { + accessorKey: 'expires_at', + header: t('worker.jobs.expiryTime'), + size: COL_SIZE_MD, + enableSorting: true, + Cell: (props) => { + return ( + + + + ); + }, + }, + { + accessorKey: 'status', + header: t('worker.jobs.status'), + size: COL_SIZE_SM, + enableSorting: true, + Cell: (props) => { + return ( + + {props.row.original.status.toLowerCase()} + + ); + }, + }, + { + accessorKey: 'assignment_id', + header: t('worker.jobs.action'), + size: COL_SIZE_MD, + enableSorting: true, + Cell: (props) => ( + + + + ), + }, + ], + [colorPalette, t, isMobile] + ); +}; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-my-jobs-filter-modal.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-my-jobs-filter-modal.tsx deleted file mode 100644 index a41ca0c491..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-my-jobs-filter-modal.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useModal } from '@/shared/contexts/modal-context'; -import { MyJobsFilterModal } from '../components/mobile/my-jobs-filter-modal'; -import { useUiConfig } from '@/shared/providers/ui-config-provider'; - -export function useMyJobFilterModal() { - const { openModal, closeModal } = useModal(); - const { uiConfig } = useUiConfig(); - - return { - openModal: () => { - openModal({ - content: ( - - ), - }); - }, - }; -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-refresh-jobs.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-refresh-jobs.ts index 30cec25ba6..c97e4bb2d2 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-refresh-jobs.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-refresh-jobs.ts @@ -1,10 +1,7 @@ -import { z } from 'zod'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import * as jobsService from '../../services/jobs.service'; import { type RefreshJobsBody } from '../../types'; -export const refreshTasksSchema = z.unknown(); - export function useRefreshJobsMutation(callbacks?: { onSuccess?: () => Promise; onError?: (error: unknown) => Promise; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-resign-job.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-resign-job.ts index c3462793b3..02fd0e966d 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-resign-job.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-resign-job.ts @@ -1,10 +1,8 @@ -import { z } from 'zod'; import { useMutation, useQueryClient } from '@tanstack/react-query'; + import * as jobsService from '../../services/jobs.service'; import { type RejectTaskBody } from '../../types'; -export const rejectTaskSchema = z.unknown(); - export function useResignJobMutation(callbacks?: { onSuccess?: () => Promise; onError?: (error: unknown) => Promise; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/my-jobs-view.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/my-jobs-view.tsx deleted file mode 100644 index 8e2500625b..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/my-jobs-view.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useIsMobile } from '@/shared/hooks'; -import { MyJobsTable } from './components/desktop'; -import { MyJobsListMobile } from './components/mobile'; - -export function MyJobsView({ chainIdsEnabled }: { chainIdsEnabled: number[] }) { - const isMobile = useIsMobile(); - - return isMobile ? ( - - ) : ( - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/my-jobs.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/my-jobs.page.tsx new file mode 100644 index 0000000000..cb814e3e61 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/my-jobs.page.tsx @@ -0,0 +1,88 @@ +import { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Stack, Typography } from '@mui/material'; +import RefreshIcon from '@mui/icons-material/Refresh'; + +import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { Button } from '@/shared/components/ui/button'; +import { MyJobsListMobile } from './components/mobile/my-jobs-list-mobile'; +import { MyJobsTable } from './components/desktop/my-jobs-table'; +import { useRefreshJobsMutation } from './hooks'; +import { StatusFilter } from './components/desktop/status-filter'; +import { MyJobsFilters } from './components/desktop/my-jobs-filters'; +import { useMyJobsFilterStore } from '../hooks'; +import { JobsSwitcherMobile } from '@/router/components/layout/protected/jobs-switcher-mobile'; + +export function MyJobsPage() { + const { colorPalette } = useColorMode(); + const isMobile = useIsMobile(); + const { t } = useTranslation(); + + const { + filterParams: { oracle_address }, + resetFilterParams, + } = useMyJobsFilterStore(); + + useEffect(() => { + return () => { + resetFilterParams(); + }; + }, [resetFilterParams]); + + const { mutate: refreshTasksMutation, isPending: isRefreshPending } = + useRefreshJobsMutation(); + + return ( + + {isMobile && ( + + + + )} + + + {t('worker.jobs.myJobs')} + + + + + + {!isMobile && } + + + + + {isMobile ? : } + + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/__tests__/get-chip-status-color.test.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/__tests__/get-chip-status-color.test.ts deleted file mode 100644 index 7c524483e0..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/__tests__/get-chip-status-color.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { describe, expect, it } from 'vitest'; -import { colorPalette } from '@/shared/styles/color-palette'; -import { getChipStatusColor } from '../get-chip-status-color'; -import { MyJobStatus, UNKNOWN_JOB_STATUS } from '../../../types'; - -describe('getChipStatusColor Function', () => { - it('should return the secondary main color for ACTIVE status', () => { - const result = getChipStatusColor(MyJobStatus.ACTIVE, colorPalette); - expect(result).toBe(colorPalette.secondary.main); - }); - - it('should return the success main color for COMPLETED status', () => { - const result = getChipStatusColor(MyJobStatus.COMPLETED, colorPalette); - expect(result).toBe(colorPalette.success.main); - }); - - it('should return the error light color for VALIDATION status', () => { - const result = getChipStatusColor(MyJobStatus.VALIDATION, colorPalette); - expect(result).toBe(colorPalette.error.light); - }); - - it('should return the error main color for unknown status', () => { - const result = getChipStatusColor(UNKNOWN_JOB_STATUS, colorPalette); - expect(result).toBe(colorPalette.error.main); - }); -}); diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/get-chip-status-color.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/get-chip-status-color.ts deleted file mode 100644 index c791586ee8..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/get-chip-status-color.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { type ColorPalette } from '@/shared/styles/color-palette'; -import { MyJobStatus, type UNKNOWN_JOB_STATUS } from '../../types'; - -export function getChipStatusColor( - status: MyJobStatus | typeof UNKNOWN_JOB_STATUS, - colorPalette: ColorPalette -) { - switch (status) { - case MyJobStatus.ACTIVE: - return colorPalette.secondary.main; - case MyJobStatus.COMPLETED: - return colorPalette.success.main; - case MyJobStatus.VALIDATION: - return colorPalette.error.light; - default: - return colorPalette.error.main; - } -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/index.ts deleted file mode 100644 index bfddc6caac..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './get-chip-status-color'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/types.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/types.ts index d2317b7cf0..977e6b9e3a 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/types.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/types.ts @@ -13,6 +13,8 @@ export enum MyJobStatus { REJECTED = 'REJECTED', } +export type StatusFilterType = '' | MyJobStatus.ACTIVE | MyJobStatus.COMPLETED; + export const UNKNOWN_JOB_STATUS = 'UNKNOWN'; export enum SortField { diff --git a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration-form.tsx b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration-form.tsx index d1e926da11..19f1e8d3ed 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration-form.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration-form.tsx @@ -1,7 +1,9 @@ -import { Box, Stack } from '@mui/material'; +import { type SubmitEvent } from 'react'; +import { Stack, Typography } from '@mui/material'; import { FormProvider, useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; import { zodResolver } from '@hookform/resolvers/zod'; + import { Button } from '@/shared/components/ui/button'; import { HCaptchaForm } from '@/shared/components/hcaptcha'; import { useOracleInstructions } from './hooks/use-oracle-instructions'; @@ -10,15 +12,17 @@ import { oracleRegistrationFormSchema, type OracleRegistrationFormValues, } from './schema'; +import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode'; export function RegistrationForm({ address, oracleInstructions, -}: Readonly<{ +}: { address: string; oracleInstructions: string | URL | null | undefined; -}>) { +}) { const { t } = useTranslation(); + const { colorPalette } = useColorMode(); const { hasViewedInstructions, handleInstructionsView } = useOracleInstructions(oracleInstructions); @@ -36,7 +40,7 @@ export function RegistrationForm({ resolver: zodResolver(oracleRegistrationFormSchema), }); - const handleSubmit = (event: React.FormEvent) => { + const handleSubmit = (event: SubmitEvent) => { void methods.handleSubmit((formData: OracleRegistrationFormValues) => { registerInOracle({ h_captcha_token: formData.h_captcha_token, @@ -49,25 +53,34 @@ export function RegistrationForm({ return ( <> - - {t('worker.registrationInExchangeOracle.completeMessage')} + + {t('worker.registrationInExchangeOracle.completeMessage')} + -
- - - - -
+ + + +
); diff --git a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration.page.tsx index e3b708a5bf..8202dbf41f 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/registration.page.tsx @@ -1,23 +1,15 @@ -import { Box, Grid, Paper, Stack } from '@mui/material'; +import { Stack, Typography } from '@mui/material'; import { useTranslation } from 'react-i18next'; -import { Navigate, useParams } from 'react-router-dom'; +import { Navigate, useNavigate, useParams } from 'react-router-dom'; + import { RegistrationForm } from '@/modules/worker/oracle-registration/registration-form'; import { Loader } from '@/shared/components/ui/loader'; import { routerPaths } from '@/router/router-paths'; import { useGetOracles } from '../hooks'; import { useIsAlreadyRegistered } from './hooks'; - -const styles = { - height: '100%', - minHeight: '70vh', - width: '100%', - boxShadow: 'none', - padding: '40px', - borderRadius: '20px', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', -}; +import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { BackButton } from '@/shared/components/ui/page-card/back-button'; function isAddress(address: string | undefined): address is string { return address !== undefined && address.length > 0; @@ -25,48 +17,75 @@ function isAddress(address: string | undefined): address is string { export function RegistrationPage() { const { t } = useTranslation(); + const { colorPalette } = useColorMode(); + const navigate = useNavigate(); + const isMobile = useIsMobile(); const { address: oracleAddress } = useParams<{ address: string }>(); + const { data, isLoading } = useGetOracles(); const { registered, isPending } = useIsAlreadyRegistered(oracleAddress); - if (isLoading || isPending) { - return ( - - - - ); - } - const oracleData = data?.find((o) => o.address === oracleAddress); + const backRoute = routerPaths.jobsDiscovery; if (oracleData === undefined || !isAddress(oracleAddress)) { - return ; + return ; } if (registered || !oracleData.registrationNeeded) { - return ( - - ); + return ; } + const handleBack = () => { + navigate(backRoute); + }; + return ( - - - - - - {t('worker.registrationInExchangeOracle.requiredMessage')} - - - - - - + + + {isMobile && } + + {t('protectedPagesHeaders.registrationInExchangeOracle')} + + + {isLoading || isPending ? ( + + + + ) : ( + + + {t('worker.registrationInExchangeOracle.requiredMessage')} + + + + )} + ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/add-api-key-modal.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/add-api-key-modal.tsx deleted file mode 100644 index f3b1ee6954..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/add-api-key-modal.tsx +++ /dev/null @@ -1,252 +0,0 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { - Autocomplete, - Box, - FormControl, - FormHelperText, - Stack, - TextField, - Typography, -} from '@mui/material'; -import { Controller, useForm } from 'react-hook-form'; -import { useTranslation } from 'react-i18next'; -import { z } from 'zod'; -import { Button } from '@/shared/components/ui/button'; -import { useIsMobile } from '@/shared/hooks'; -import { - useEnrollExchangeApiKeys, - useGetSupportedExchanges, -} from '../../hooks/use-exchange-api-keys'; -import { ModalError, ModalLoading, ModalSuccess } from './modal-states'; -import { useEffect } from 'react'; - -interface AddApiKeyModalProps { - onClose: () => void; - disableClose: (disable: boolean) => void; -} - -export function AddApiKeyModal({ onClose, disableClose }: AddApiKeyModalProps) { - const { t } = useTranslation(); - const { - mutate: enrollExchangeApiKey, - reset: resetMutation, - error, - isSuccess, - isPending, - isError, - isIdle, - } = useEnrollExchangeApiKeys(); - const isMobile = useIsMobile(); - const { data: supportedExchanges } = useGetSupportedExchanges(); - - useEffect(() => { - disableClose(isPending); - }, [isPending, disableClose]); - - const { - control, - handleSubmit, - formState: { errors }, - reset, - } = useForm({ - defaultValues: { - exchange: '', - apiKey: '', - secretKey: '', - }, - resolver: zodResolver( - z.object({ - exchange: z.string().min(1, t('validation.required')), - apiKey: z.string().trim().min(1, t('validation.required')), - secretKey: z.string().trim().min(1, t('validation.required')), - }) - ), - }); - - useEffect(() => { - return () => { - reset(); - resetMutation(); - }; - }, [reset, resetMutation]); - - const onSubmit = (data: { - exchange: string; - apiKey: string; - secretKey: string; - }) => { - enrollExchangeApiKey(data); - }; - - return ( -
- - - {isMobile - ? t('worker.profile.apiKeyData.connectApiKey') - : t('worker.profile.apiKeyData.connectYourApiKey')} - - {isPending && } - {isIdle && ( - <> - - {t('worker.profile.apiKeyData.modalDescription')} - - - - ( - exchange.name) || - [] - } - getOptionLabel={(option) => { - const exchange = supportedExchanges?.find( - (exchange) => exchange.name === option - ); - return exchange?.display_name || option || ''; - }} - renderInput={(params) => ( - - )} - renderOption={(props, option) => { - const exchange = supportedExchanges?.find( - (exchange) => exchange.name === option - ); - return ( - - - {exchange?.display_name || exchange?.name} - - - ); - }} - {...field} - onChange={(_, value) => field.onChange(value)} - /> - )} - /> - {errors.exchange && ( - - {errors.exchange.message} - - )} - - - ( - - )} - /> - {errors.apiKey && ( - {errors.apiKey.message} - )} - - - - ( - - )} - /> - {errors.secretKey && ( - - {errors.secretKey.message} - - )} - - - )} - {isSuccess && ( - - - {t('worker.profile.apiKeyData.connectSuccess')} - - - )} - {isError && ( - - )} - {isIdle && ( - - )} - {(isPending || isSuccess) && ( - - )} - {isError && ( - - )} - - {t('worker.profile.apiKeyData.modalFooterAgreement')} - - -
- ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/api-key-data.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/api-key-data.tsx deleted file mode 100644 index a6fa175b58..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/api-key-data.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { IconButton, Stack, Typography } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { Chip } from '@/shared/components/ui/chip'; -import { CustomTextField, CustomTextFieldDark } from './custom-text-field'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { DeleteIcon, EditIcon } from '@/shared/components/ui/icons'; -import { - useDeleteApiKeyModal, - useEditApiKeyModal, -} from '../hooks/use-api-key-modals'; -import { useGetExchangeApiKeys } from '../../hooks/use-exchange-api-keys'; - -export function ApiKeyData({ - stakingExchangeError, -}: { - stakingExchangeError?: string; -}) { - const { isDarkMode } = useColorMode(); - const { t } = useTranslation(); - const { openModal: openEditApiKeyModal } = useEditApiKeyModal(); - const { openModal: openDeleteApiKeyModal } = useDeleteApiKeyModal(); - const { data: exchangeApiKeyData, isError: isExchangeApiKeyError } = - useGetExchangeApiKeys(); - - const textField = isDarkMode ? ( - - ) : ( - - ); - - return ( - - - - {t('worker.profile.apiKeyData.apiKey')} - - - - - {textField} - {exchangeApiKeyData?.exchange_name && ( - - - openEditApiKeyModal(exchangeApiKeyData.exchange_name) - } - > - - - - - - - )} - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/index.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/index.ts deleted file mode 100644 index bc7560332f..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './start-idv-btn'; -export * from './register-address-btn'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/register-address-btn.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/register-address-btn.tsx index b610f0c2bc..21973a3fd7 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/register-address-btn.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/register-address-btn.tsx @@ -12,10 +12,11 @@ export function RegisterAddressBtn() { return ( diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/start-idv-btn.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/start-idv-btn.tsx deleted file mode 100644 index 68f5ad06e9..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/buttons/start-idv-btn.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { t } from 'i18next'; -import { Button } from '@/shared/components/ui/button'; -import { useStartIdv } from '../../hooks'; - -export function StartIdvBtn() { - const { isIdvAlreadyInProgress, idvStartIsPending, startIdv } = useStartIdv(); - - if (isIdvAlreadyInProgress) { - return ( - - ); - } - - return ( - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/custom-text-field.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/custom-text-field.tsx deleted file mode 100644 index de9183c226..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/custom-text-field.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { colorPalette } from '@/shared/styles/color-palette'; -import { - darkColorPalette, - onlyDarkModeColor, -} from '@/shared/styles/dark-color-palette'; -import { styled, TextField } from '@mui/material'; - -const CustomTextField = styled(TextField)(() => ({ - '& .Mui-disabled': { - height: '48px', - maxWidth: '376px', - color: colorPalette.text.disabledSecondary, - WebkitTextFillColor: `${colorPalette.text.disabledSecondary} !important`, - }, - '& .MuiOutlinedInput-root': { - '& fieldset': { - border: '1px dashed', - borderColor: `${colorPalette.text.primary} !important`, - color: colorPalette.text.disabledSecondary, - WebkitTextFillColor: colorPalette.text.disabledSecondary, - }, - }, -})); - -const CustomTextFieldDark = styled(TextField)(() => ({ - '& .Mui-disabled': { - height: '48px', - maxWidth: '376px', - color: darkColorPalette.text.disabledSecondary, - WebkitTextFillColor: `${darkColorPalette.text.disabledSecondary} !important`, - }, - '& .MuiOutlinedInput-root': { - '& fieldset': { - border: '1px dashed', - borderColor: `${onlyDarkModeColor.mainColorWithOpacity} !important`, - color: darkColorPalette.text.disabledSecondary, - WebkitTextFillColor: darkColorPalette.text.disabledSecondary, - }, - }, -})); - -export { CustomTextField, CustomTextFieldDark }; diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/delete-api-key-modal.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/delete-api-key-modal.tsx deleted file mode 100644 index 6a75d44423..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/delete-api-key-modal.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { Stack, Typography } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { Button } from '@/shared/components/ui/button'; -import { useIsMobile } from '@/shared/hooks'; -import { useDeleteExchangeApiKeys } from '../../hooks/use-exchange-api-keys'; -import { useEffect } from 'react'; -import { ModalError, ModalSuccess, ModalLoading } from './modal-states'; - -interface DeleteApiKeyModalProps { - onClose: () => void; - disableClose: (disable: boolean) => void; -} - -export function DeleteApiKeyModal({ - onClose, - disableClose, -}: DeleteApiKeyModalProps) { - const { t } = useTranslation(); - const { - mutate: deleteExchangeApiKey, - reset: resetMutation, - isSuccess, - isError, - isPending, - isIdle, - } = useDeleteExchangeApiKeys(); - const isMobile = useIsMobile(); - - const handleDeleteExchangeApiKey = () => { - deleteExchangeApiKey(); - }; - - useEffect(() => { - disableClose(isPending); - }, [isPending, disableClose]); - - useEffect(() => { - return () => { - resetMutation(); - }; - }, [resetMutation]); - - return ( - - - {t('worker.profile.apiKeyData.deleteApiKey')} - - {isPending && } - {isIdle && ( - <> - - {t('worker.profile.apiKeyData.deleteApiKeyConfirmation')} - - - {t('worker.profile.apiKeyData.deleteApiKeyDescription')} - - - )} - {isSuccess && ( - - - {t('worker.profile.apiKeyData.deleteKeySuccess')} - - - )} - {isError && ( - - )} - {isIdle && ( - - - - - )} - {(isPending || isSuccess) && ( - - )} - {isError && ( - - )} - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/edit-api-key-modal.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/edit-api-key-modal.tsx deleted file mode 100644 index 9141eb9b02..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/edit-api-key-modal.tsx +++ /dev/null @@ -1,280 +0,0 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { - Autocomplete, - Box, - FormControl, - FormHelperText, - Stack, - TextField, - Typography, -} from '@mui/material'; -import { Controller, useForm } from 'react-hook-form'; -import { useTranslation } from 'react-i18next'; -import { z } from 'zod'; -import { Button } from '@/shared/components/ui/button'; -import { useIsMobile } from '@/shared/hooks'; -import { useEffect } from 'react'; -import { - useEnrollExchangeApiKeys, - useGetSupportedExchanges, -} from '../../hooks/use-exchange-api-keys'; -import { ModalError, ModalLoading, ModalSuccess } from './modal-states'; - -interface EditApiKeyModalProps { - exchangeName: string; - onClose: () => void; - disableClose: (disable: boolean) => void; -} - -export function EditApiKeyModal({ - onClose, - exchangeName, - disableClose, -}: EditApiKeyModalProps) { - const { t } = useTranslation(); - const isMobile = useIsMobile(); - const { - mutate: postExchangeApiKey, - reset: resetMutation, - error, - isSuccess, - isError, - isPending, - isIdle, - } = useEnrollExchangeApiKeys(); - const { data: supportedExchanges } = useGetSupportedExchanges(); - - const { - control, - handleSubmit, - formState: { errors }, - reset, - } = useForm({ - defaultValues: { - exchange: '', - apiKey: '', - secretKey: '', - }, - resolver: zodResolver( - z.object({ - exchange: z.string().min(1, t('validation.required')), - apiKey: z.string().trim().min(1, t('validation.required')), - secretKey: z.string().trim().min(1, t('validation.required')), - }) - ), - }); - - useEffect(() => { - if (exchangeName && supportedExchanges) { - reset({ - exchange: exchangeName, - apiKey: '', - secretKey: '', - }); - } - }, [exchangeName, reset, supportedExchanges]); - - useEffect(() => { - disableClose(isPending); - }, [isPending, disableClose]); - - useEffect(() => { - return () => { - reset(); - resetMutation(); - }; - }, [reset, resetMutation]); - - const onSubmit = (data: { - exchange: string; - apiKey: string; - secretKey: string; - }) => { - postExchangeApiKey(data); - }; - - return ( -
- - - {t('worker.profile.apiKeyData.editApiKey')} - - {isPending && } - {isIdle && ( - <> - - {t('worker.profile.apiKeyData.modalDescription')} - - - - ( - exchange.name) || - [] - } - getOptionLabel={(option) => { - const exchange = supportedExchanges?.find( - (exchange) => exchange.name === option - ); - return exchange?.display_name || option || ''; - }} - renderInput={(params) => ( - - )} - renderOption={(props, option) => { - const exchange = supportedExchanges?.find( - (exchange) => exchange.name === option - ); - return ( - - - {exchange?.display_name || exchange?.name} - - - ); - }} - {...field} - onChange={(_, value) => field.onChange(value)} - /> - )} - /> - {errors.exchange && ( - - {errors.exchange.message} - - )} - - - ( - - )} - /> - {errors.apiKey && ( - {errors.apiKey.message} - )} - - - - ( - - )} - /> - {errors.secretKey && ( - - {errors.secretKey.message} - - )} - - - )} - {isSuccess && ( - - - {t('worker.profile.apiKeyData.editSuccess')} - - - )} - {isError && ( - - )} - {isIdle && ( - - - - - )} - {(isPending || isSuccess) && ( - - )} - {isError && ( - - )} - -
- ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/identity-verification-control.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/identity-verification-control.tsx index 6acd2ab252..23b63457d3 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/identity-verification-control.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/components/identity-verification-control.tsx @@ -1,37 +1,229 @@ -import { useTranslation } from 'react-i18next'; -import { Stack, Typography } from '@mui/material'; -import { Chip } from '@/shared/components/ui/chip'; -import { useWorkerIdentityVerificationStatus } from '../hooks'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { useTranslation, Trans } from 'react-i18next'; +import { Box, keyframes, Link, Stack, Typography } from '@mui/material'; +import { jwtDecode } from 'jwt-decode'; + +import { useStartIdv } from '../hooks'; import { KycStatus } from '../types'; -import { StartIdvBtn } from './buttons'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { env } from '@/shared/env'; +import { HourglassIcon, VeriffIcon } from '@/shared/components/ui/icons'; +import { Button } from '@/shared/components/ui/button'; +import { useAccessTokenRefresh } from '@/api/hooks/use-access-token-refresh'; +import { browserAuthProvider } from '@/shared/contexts/browser-auth-provider'; +import { UserData } from '@/modules/auth/context/auth-context'; +import { useAuth } from '@/modules/auth/hooks/use-auth'; -const getChipColor = (status: KycStatus) => { - if (status === KycStatus.APPROVED) { - return 'success.main'; - } else if ( - [KycStatus.RESUBMISSION_REQUESTED, KycStatus.REVIEW].includes(status) - ) { - return 'warning.main'; +const hourglassSpin = keyframes` + 0% { + transform: rotate(0deg); + } + 45% { + transform: rotate(180deg); + } + 55% { + transform: rotate(180deg); + } + 100% { + transform: rotate(360deg); } - return 'error.main'; -}; +`; + +// TODO: Extend to 30 seconds +const CHECK_STATUS_COOLDOWN_TIME = 10000; + +export function IdentityVerificationControl({ + kycStatus, + onKycApproved, +}: { + kycStatus: KycStatus; + onKycApproved: () => void; +}) { + const [isCheckStatusCoolingDown, setIsCheckStatusCoolingDown] = + useState(false); + + const cooldownTimerRef = useRef(null); -export function IdentityVerificationControl() { const { t } = useTranslation(); - const { status } = useWorkerIdentityVerificationStatus(); + const { isIdvAlreadyInProgress, idvStarted, idvStartIsPending, startIdv } = + useStartIdv(); + const { refreshAccessTokenAsync, isRefreshingAccessToken } = + useAccessTokenRefresh(); + const { updateUserData } = useAuth(); + const { colorPalette } = useColorMode(); + + const handleCheckVerificationStatus = useCallback(async () => { + if (isRefreshingAccessToken || isCheckStatusCoolingDown) { + return; + } + + setIsCheckStatusCoolingDown(true); + cooldownTimerRef.current = setTimeout(() => { + setIsCheckStatusCoolingDown(false); + cooldownTimerRef.current = null; + }, CHECK_STATUS_COOLDOWN_TIME); - const label = t(`worker.profile.idvStatusValues.${status}`); + try { + await refreshAccessTokenAsync(); + const accessToken = browserAuthProvider.getAccessToken(); - if (status === KycStatus.NONE) { - return ; + if (!accessToken) { + return; + } + + const userData = jwtDecode(accessToken); + + if (!!userData.kyc_status && kycStatus !== userData.kyc_status) { + updateUserData(userData); + } + + if (userData.kyc_status === KycStatus.APPROVED) { + onKycApproved(); + } + } catch (error) { + console.error(error); + } + }, [ + onKycApproved, + refreshAccessTokenAsync, + updateUserData, + kycStatus, + isRefreshingAccessToken, + isCheckStatusCoolingDown, + ]); + + useEffect(() => { + return () => { + if (cooldownTimerRef.current) { + clearTimeout(cooldownTimerRef.current); + cooldownTimerRef.current = null; + } + }; + }, []); + + if (kycStatus === KycStatus.DECLINED) { + return ( + + , + }} + i18nKey="worker.profile.verificationDeclined" + /> + + ); + } + + if ( + (isIdvAlreadyInProgress || idvStarted) && + kycStatus !== KycStatus.RESUBMISSION_REQUESTED + ) { + return ( + <> + + + {t('worker.profile.verificationOpenedInNewTab')} + + + {t('worker.profile.completeYourVerification')} + + + + + + + {t('worker.profile.waitingForVerification')} + + + + + + + + ); } return ( - - - {t('worker.profile.identityVerificationStatus')} + + + {t('worker.profile.veriffCopy')} - + + + {t('worker.profile.poweredBy')} + + + + ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/index.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/components/index.ts index 7df62f04b8..48f2077003 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/index.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/components/index.ts @@ -1,10 +1,3 @@ -export * from './wallet-connect-done'; export * from './profile-data'; export * from './identity-verification-control'; export * from './wallet-connection-control'; -export * from './staking-info'; -export * from './api-key-data'; -export * from './custom-text-field'; -export * from './add-api-key-modal'; -export * from './edit-api-key-modal'; -export * from './delete-api-key-modal'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/modal-states.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/modal-states.tsx deleted file mode 100644 index 5225e3bd2b..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/modal-states.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import CheckIcon from '@mui/icons-material/CheckCircle'; -import { Loader } from '@/shared/components/ui/loader'; -import Box from '@mui/material/Box'; -import Typography from '@mui/material/Typography'; -import CloseIcon from '@mui/icons-material/Cancel'; - -export function ModalLoading() { - return ( - - - - ); -} - -export function ModalSuccess({ children }: { children: React.ReactNode }) { - return ( - <> - - - - {children} - - ); -} - -export function ModalError({ message }: { message: string }) { - return ( - <> - - - - - {message} - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/profile-bottom-tray.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/profile-bottom-tray.tsx new file mode 100644 index 0000000000..defe9b1fa1 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/components/profile-bottom-tray.tsx @@ -0,0 +1,51 @@ +import { useColorMode } from '@/shared/contexts/color-mode'; +import { Stack } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { ProfileData } from './profile-data'; +import { Button } from '@/shared/components/ui/button'; +import { HelpIcon } from '@/shared/components/ui/icons'; +import { MOBILE_BOTTOM_TRAY_HEIGHT } from '@/shared/consts'; + +export function ProfileBottomTray() { + const { colorPalette } = useColorMode(); + const { t } = useTranslation(); + + return ( + theme.zIndex.appBar, + }} + > + + + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/profile-data.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/profile-data.tsx index 42278a97c8..ec9db397b2 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/profile-data.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/components/profile-data.tsx @@ -1,50 +1,128 @@ -import { Stack, Typography } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { Link } from 'react-router-dom'; +import { Box, Link, Stack, Typography } from '@mui/material'; +import { Link as RouterLink, useLocation } from 'react-router-dom'; + import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; -import { Button } from '@/shared/components/ui/button'; -import { routerPaths } from '@/router/router-paths'; import { useColorMode } from '@/shared/contexts/color-mode'; -import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette'; +import { shortenEscrowAddress } from '@/shared/helpers/evm'; +import { routerPaths } from '@/router/router-paths'; + +function Wrapper({ + isCompact, + isProfilePage, + children, +}: { + isCompact: boolean; + isProfilePage: boolean; + children: React.ReactNode; +}) { + if (isCompact && !isProfilePage) { + return ( + + {children} + + ); + } + + return <>{children}; +} -export function ProfileData() { - const { colorPalette, isDarkMode } = useColorMode(); +export function ProfileData({ + variant = 'compact', +}: { + variant?: 'compact' | 'expanded'; +}) { const { user } = useAuthenticatedUser(); - const { t } = useTranslation(); + const { colorPalette } = useColorMode(); + const location = useLocation(); + + const isProfilePage = location.pathname === routerPaths.profile; + const isCompact = variant === 'compact'; + return ( - - - {t('worker.profile.email')} - + + - {user.email} - - - - - {t('worker.profile.password')} - - + + {user.email} + + + {shortenEscrowAddress( + user.wallet_address ?? '', + isCompact ? 9 : 6, + isCompact ? 8 : 5 + )} + + - + ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/staking-info.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/staking-info.tsx deleted file mode 100644 index 889d26aaae..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/staking-info.tsx +++ /dev/null @@ -1,225 +0,0 @@ -import { - Button, - IconButton, - Link, - Skeleton, - Stack, - Typography, -} from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { Chip } from '@/shared/components/ui/chip'; -import { env } from '@/shared/env'; -import { useAddApiKeyModal } from '../hooks/use-api-key-modals'; -import { ApiKeyData } from './'; -import { useGetStakingSummary } from '../../hooks/use-staking'; -import { RefreshIcon } from '@/shared/components/ui/icons'; -import { useGetExchangeApiKeys } from '../../hooks/use-exchange-api-keys'; -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; -import { useEffect, useRef, useState } from 'react'; -import { useAccessTokenRefresh } from '@/api/hooks/use-access-token-refresh'; -import { colorPalette } from '@/shared/styles/color-palette'; -import { useGetUiConfig } from '@/shared/hooks/use-get-ui-config'; -import { - TopNotificationType, - useNotification, -} from '@/shared/hooks/use-notification'; - -export function StakingInfo() { - const [isPromptExpanded, setIsPromptExpanded] = useState(false); - const tokenRefreshLock = useRef(false); - - const { user, updateUserData } = useAuthenticatedUser(); - const { refreshAccessTokenAsync } = useAccessTokenRefresh(); - const { t } = useTranslation(); - const { showNotification } = useNotification(); - - const { openModal: openAddApiKeyModal } = useAddApiKeyModal(); - const { data: exchangeApiKeyData, isLoading: isExchangeApiKeyLoading } = - useGetExchangeApiKeys(); - const { - data: stakingSummary, - isLoading, - isError, - refetch, - isRefetching, - } = useGetStakingSummary(); - const { data: uiConfig, isLoading: isUiConfigLoading } = useGetUiConfig(); - - const isConnectButtonDisabled = - !!exchangeApiKeyData?.exchange_name || isExchangeApiKeyLoading; - - const stakedAmount = - Number(stakingSummary?.on_chain_stake || 0) + - Number(stakingSummary?.exchange_stake || 0); - - const isStaked = - isLoading || isError || isUiConfigLoading - ? false - : stakedAmount >= Number(uiConfig?.minThreshold || '0'); - - useEffect(() => { - const stakingSummaryError = - stakingSummary?.on_chain_error || stakingSummary?.exchange_error; - if (stakingSummaryError && !isLoading && !isRefetching) { - showNotification({ - type: TopNotificationType.WARNING, - message: stakingSummaryError, - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [ - isLoading, - isRefetching, - stakingSummary?.on_chain_error, - stakingSummary?.exchange_error, - ]); - - useEffect(() => { - if (isRefetching || isLoading) return; - - if (isStaked !== user.is_stake_eligible) { - if (!tokenRefreshLock.current) { - tokenRefreshLock.current = true; - updateUserData({ is_stake_eligible: isStaked }); - void refreshAccessTokenAsync({ authType: 'web2' }); - } - } else { - tokenRefreshLock.current = false; - } - }, [ - isStaked, - user.is_stake_eligible, - refreshAccessTokenAsync, - isRefetching, - isLoading, - updateUserData, - ]); - - const handleRefreshStakingInfo = () => { - if (isRefetching || isLoading) return; - tokenRefreshLock.current = false; - refetch(); - }; - - return ( - - - - {t('worker.profile.stakingInfo.stakeHmt')} - - {isLoading || isRefetching ? ( - - ) : ( - - )} - - - {isPromptExpanded - ? t('worker.profile.stakingInfo.prompt', { - amount: uiConfig?.minThreshold, - }) - : t('worker.profile.stakingInfo.promptShort')}{' '} - {isPromptExpanded && ( - <> -
- - {t('worker.profile.stakingInfo.howToCreateApiKeys')} - -
- - )} - -
- - - {t('worker.profile.stakingInfo.stakedAmount')} - - svg > path': { - fill: - isRefetching || isLoading - ? colorPalette.button.disabled - : 'primary.main', - }, - }} - onClick={handleRefreshStakingInfo} - > - - - - {isLoading || isRefetching ? ( - - ) : ( - - {stakedAmount} HMT - - )} - - - - - -
- ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/wallet-connect-done.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/wallet-connect-done.tsx deleted file mode 100644 index f44d5580ed..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/wallet-connect-done.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { IconButton, Stack, Tooltip, Typography } from '@mui/material'; -import { t } from 'i18next'; -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { useWalletConnect } from '@/shared/contexts/wallet-connect'; -import { Chip } from '@/shared/components/ui/chip'; -import { CopyIcon } from '@/shared/components/ui/icons'; -import { MouseEvent, useRef, useState } from 'react'; -import { shortenEscrowAddress } from '@/shared/helpers/evm'; -import { CustomTextField, CustomTextFieldDark } from './custom-text-field'; - -export function WalletConnectDone() { - const [isCopied, setIsCopied] = useState(false); - const timeoutRef = useRef(null); - const { isDarkMode } = useColorMode(); - const { address } = useWalletConnect(); - const { - user: { wallet_address }, - } = useAuthenticatedUser(); - - if (!wallet_address) { - return null; - } - - const shortAddress = shortenEscrowAddress(wallet_address, 6, 6); - - const handleCopyClick = (e: MouseEvent) => { - if (isCopied) return; - - e.stopPropagation(); - navigator.clipboard.writeText(wallet_address); - setIsCopied(true); - - if (timeoutRef.current) { - clearTimeout(timeoutRef.current); - } - - timeoutRef.current = setTimeout(() => { - setIsCopied(false); - }, 1500); - }; - - const textFiled = isDarkMode ? ( - - - - - - ), - }} - /> - ) : ( - - - - - - ), - }} - /> - ); - - return ( - - - - {t('worker.profile.wallet')} - - - - {address && !wallet_address ? null : textFiled} - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/wallet-connection-control.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/wallet-connection-control.tsx index 8188f76887..5f8cabf303 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/wallet-connection-control.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/components/wallet-connection-control.tsx @@ -1,40 +1,36 @@ import { useTranslation } from 'react-i18next'; -import { Grid } from '@mui/material'; +import { Stack, Typography } from '@mui/material'; import { Button } from '@/shared/components/ui/button'; -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; import { useWalletConnect } from '@/shared/contexts/wallet-connect'; -import { useWorkerIdentityVerificationStatus } from '../hooks'; -import { WalletConnectDone } from './wallet-connect-done'; -import { RegisterAddressBtn } from './buttons'; +import { RegisterAddressBtn } from './buttons/register-address-btn'; +import { useAuth } from '@/modules/auth/hooks/use-auth'; +import { useColorMode } from '@/shared/contexts/color-mode'; export function WalletConnectionControl() { const { t } = useTranslation(); - const { user } = useAuthenticatedUser(); - const { isVerificationCompleted } = useWorkerIdentityVerificationStatus(); + const { user } = useAuth(); const { isConnected, openModal } = useWalletConnect(); + const { colorPalette } = useColorMode(); - const { wallet_address: walletAddress } = user; - const hasWalletAddress = Boolean(walletAddress); + const hasWalletAddress = !!user?.wallet_address; - if (hasWalletAddress) { - return ; - } - - if (isVerificationCompleted && isConnected) { + if (isConnected && !hasWalletAddress) { return ( - + - {t('worker.profile.walletAddressMessage')} - + + {t('worker.profile.walletAddressMessage')} + + ); } return ( diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/index.ts index 77e3af8ebd..bae54c609e 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/index.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/index.ts @@ -1,2 +1 @@ -export * from './use-worker-profile-status'; export * from './use-start-idv'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-api-key-modals.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-api-key-modals.tsx deleted file mode 100644 index 5d7db03586..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-api-key-modals.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useModal } from '@/shared/contexts/modal-context'; -import { - AddApiKeyModal, - EditApiKeyModal, - DeleteApiKeyModal, -} from '../components'; - -export function useAddApiKeyModal() { - const { openModal, closeModal, setDisableClose } = useModal(); - - return { - openModal: () => - openModal({ - content: ( - - ), - }), - }; -} - -export function useEditApiKeyModal() { - const { openModal, closeModal, setDisableClose } = useModal(); - - return { - openModal: (exchangeName: string) => - openModal({ - content: ( - - ), - }), - }; -} - -export function useDeleteApiKeyModal() { - const { openModal, closeModal, setDisableClose } = useModal(); - - return { - openModal: () => - openModal({ - content: ( - - ), - }), - }; -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-idv-mutation.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-idv-mutation.ts index 55a9fed223..c64cb15770 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-idv-mutation.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-idv-mutation.ts @@ -1,12 +1,12 @@ import { useMutation } from '@tanstack/react-query'; -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; import * as profileService from '../services/profile.service'; +import { useAuth } from '@/modules/auth/hooks/use-auth'; export function useIdvStartMutation() { - const { user } = useAuthenticatedUser(); + const { user } = useAuth(); return useMutation({ mutationFn: async () => profileService.startIdv(), - mutationKey: ['idvStart', user.email], + mutationKey: ['idvStart', user?.email], }); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-idv.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-idv.ts index ca9bc21805..ebb47a41d8 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-idv.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-idv.ts @@ -38,7 +38,7 @@ export function useStartIdv() { } if (idvStarted && idvStartData.url) { - window.location.href = idvStartData.url; + window.open(idvStartData.url, '_blank', 'noopener,noreferrer'); } }, [ idvStartData?.url, @@ -50,6 +50,7 @@ export function useStartIdv() { return { isIdvAlreadyInProgress, + idvStarted, idvStartIsPending, startIdv, }; diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-worker-profile-status.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-worker-profile-status.tsx deleted file mode 100644 index 8e355d2bed..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-worker-profile-status.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; -import { KycStatus, type WorkerItentityVerificationStatus } from '../types'; - -export function useWorkerIdentityVerificationStatus(): WorkerItentityVerificationStatus { - const { user } = useAuthenticatedUser(); - - return { - isVerificationCompleted: user.kyc_status === KycStatus.APPROVED, - status: user.kyc_status ?? KycStatus.NONE, - }; -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/types/profile-types.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/types/profile-types.ts index cdaf2dd700..0bfeebdb00 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/types/profile-types.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/types/profile-types.ts @@ -7,8 +7,3 @@ export enum KycStatus { EXPIRED = 'expired', ABANDONED = 'abandoned', } - -export interface WorkerItentityVerificationStatus { - isVerificationCompleted: boolean; - status: (typeof KycStatus)[keyof typeof KycStatus]; -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/views/profile.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/views/profile.page.tsx index 3fa42e64b9..fcee561141 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/views/profile.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/views/profile.page.tsx @@ -1,29 +1,45 @@ -import { Paper, Stack } from '@mui/material'; import { useEffect } from 'react'; +import { Box, Stack, Typography } from '@mui/material'; import { t } from 'i18next'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { Link, useNavigate } from 'react-router-dom'; + import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; import { useWalletConnect } from '@/shared/contexts/wallet-connect'; import { TopNotificationType, useNotification, } from '@/shared/hooks/use-notification'; -import { - ProfileData, - IdentityVerificationControl, - WalletConnectionControl, - StakingInfo, -} from '../components'; -import { PageCardLoader } from '@/shared/components/ui/page-card/page-card-loader'; -import { useUiConfig } from '@/shared/providers/ui-config-provider'; +import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode'; +import { ProfileData } from '../components/profile-data'; +import { CheckmarkIcon, LogoutIcon } from '@/shared/components/ui/icons'; +import { shortenEscrowAddress } from '@/shared/helpers/evm'; +import { Button } from '@/shared/components/ui/button'; +import { browserAuthProvider } from '@/shared/contexts/browser-auth-provider'; +import { routerPaths } from '@/router/router-paths'; +import { BackButton } from '@/shared/components/ui/page-card/back-button'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { CopyToClipboardButton } from '@/shared/components/ui/copy-to-clipboard-button'; -export function WorkerProfilePage() { +export function ProfilePage() { const { user } = useAuthenticatedUser(); - const isMobile = useIsMobile(); + const { colorPalette } = useColorMode(); const { isConnected, initializing, web3ProviderMutation } = useWalletConnect(); const { showNotification } = useNotification(); - const { uiConfig, isUiConfigLoading } = useUiConfig(); + const isMobile = useIsMobile(); + const navigate = useNavigate(); + + const handleBack = () => { + navigate(routerPaths.jobsDiscovery); + }; + + const handleSignOut = () => { + browserAuthProvider.signOut({ + callback: () => { + window.location.reload(); + }, + }); + }; useEffect(() => { if (initializing) return; @@ -50,31 +66,150 @@ export function WorkerProfilePage() { showNotification, ]); - if (isUiConfigLoading) { - return ; - } - return ( - - - - - - {!!user.wallet_address && uiConfig?.stakingEligibilityEnabled && ( - - )} + + + {isMobile && } + + {t('worker.profile.profileHeader')} + + + + + + + + {t('worker.profile.accountVerified')} + + + + + + + {t('worker.profile.email')} + + + {user.email} + + + + + {t('worker.profile.walletAddress')} + + + {shortenEscrowAddress(user.wallet_address ?? '', 9, 8)} + + + + + + + + + - + ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/providers/require-stake.tsx b/packages/apps/human-app/frontend/src/modules/worker/providers/require-stake.tsx deleted file mode 100644 index 83802a5467..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/providers/require-stake.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import type { ReactNode } from 'react'; -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; -import { routerPaths } from '@/router/router-paths'; -import { protectedRoutes } from '@/router/routes'; -import { useUiConfig } from '@/shared/providers/ui-config-provider'; -import { Navigate, useLocation, matchPath } from 'react-router-dom'; - -const stakeProtectedPaths = protectedRoutes - .map((route) => route.routerProps.path) - .filter((path) => path !== routerPaths.worker.profile); - -export function RequireStake({ children }: Readonly<{ children: ReactNode }>) { - const { user } = useAuthenticatedUser(); - const location = useLocation(); - const { uiConfig } = useUiConfig(); - - const isStakeProtectedRoute = stakeProtectedPaths.some( - (path) => path && matchPath(path, location.pathname) - ); - - if ( - uiConfig?.stakingEligibilityEnabled && - !user?.is_stake_eligible && - isStakeProtectedRoute - ) { - return ; - } - - return children; -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/reset-password/hooks/reset-password.ts b/packages/apps/human-app/frontend/src/modules/worker/reset-password/hooks/reset-password.ts index f8e09d9668..4a6437283c 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/reset-password/hooks/reset-password.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/reset-password/hooks/reset-password.ts @@ -1,12 +1,10 @@ -import { z } from 'zod'; import { useMutation } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; + import { routerPaths } from '@/router/router-paths'; import * as passwordService from '../password.service'; import { type ResetPasswordDto } from '../types'; -export const ResetPasswordSuccessResponseSchema = z.unknown(); - export function useResetPasswordMutation() { const navigate = useNavigate(); @@ -15,7 +13,7 @@ export function useResetPasswordMutation() { data: Omit & { token: string } ) => passwordService.resetPassword(data), onSuccess: () => { - navigate(routerPaths.worker.resetPasswordSuccess); + navigate(routerPaths.resetPasswordSuccess); }, }); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/reset-password/reset-password-success.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/reset-password/reset-password-success.page.tsx index b499e83aeb..bc292347ff 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/reset-password/reset-password-success.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/reset-password/reset-password-success.page.tsx @@ -1,52 +1,86 @@ -import Grid from '@mui/material/Grid'; +import { useEffect } from 'react'; +import { Paper, Stack, Typography } from '@mui/material'; import { t } from 'i18next'; import { Link } from 'react-router-dom'; -import Typography from '@mui/material/Typography'; import CheckCircle from '@mui/icons-material/CheckCircle'; -import { useEffect } from 'react'; + import { Button } from '@/shared/components/ui/button'; import { routerPaths } from '@/router/router-paths'; -import { PageCard } from '@/shared/components/ui/page-card'; import { useAuth } from '@/modules/auth/hooks/use-auth'; import { useColorMode } from '@/shared/contexts/color-mode'; -export function ResetPasswordWorkerSuccessPage() { +export function ResetPasswordSuccessPage() { const { colorPalette } = useColorMode(); - const { signOut } = useAuth(); + const { signOut, user } = useAuth(); useEffect(() => { - signOut(); - }, [signOut]); + if (user) { + signOut(); + } + }, [user, signOut]); + return ( - - {t('worker.resetPasswordSuccess.title')} + + + - - } - > - - - {t('worker.resetPasswordSuccess.description')} - - - - + + {t('worker.resetPasswordSuccess.title')} + + + + + {t('worker.resetPasswordSuccess.description')} + + + + + ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/reset-password/reset-password.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/reset-password/reset-password.page.tsx index afab645953..14788a7065 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/reset-password/reset-password.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/reset-password/reset-password.page.tsx @@ -1,15 +1,14 @@ +import { useEffect } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; -import Grid from '@mui/material/Grid'; -import Typography from '@mui/material/Typography'; +import { Box, Paper, Stack, Typography } from '@mui/material'; import { t } from 'i18next'; import omit from 'lodash/omit'; -import { useLocation } from 'react-router-dom'; +import { useLocation, useNavigate } from 'react-router-dom'; import queryString from 'query-string'; + import { Button } from '@/shared/components/ui/button'; -import { Password } from '@/shared/components/data-entry/password/password'; -import { PageCard } from '@/shared/components/ui/page-card'; -import { Alert } from '@/shared/components/ui/alert'; +import { Password } from '@/shared/components/data-entry/password'; import { getErrorMessageForError } from '@/shared/errors'; import { routerPaths } from '@/router/router-paths'; import { HCaptchaForm } from '@/shared/components/hcaptcha'; @@ -17,10 +16,19 @@ import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors import { useResetPasswordMutation } from './hooks'; import { resetPasswordDtoSchema } from './schemas'; import { type ResetPasswordDto } from './types'; +import { BackButton } from '@/shared/components/ui/page-card/back-button'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { + TopNotificationType, + useNotification, +} from '@/shared/hooks/use-notification'; -export function ResetPasswordWorkerPage() { +export function ResetPasswordPage() { const location = useLocation(); + const navigate = useNavigate(); const { token } = queryString.parse(location.search); + const { colorPalette } = useColorMode(); + const { showNotification } = useNotification(); const methods = useForm({ defaultValues: { @@ -47,52 +55,99 @@ export function ResetPasswordWorkerPage() { ); }; + const handleBackButton = () => { + navigate(routerPaths.profile); + }; + + useEffect(() => { + if (isResetPasswordWorkerError) { + showNotification({ + type: TopNotificationType.ERROR, + message: getErrorMessageForError(resetPasswordWorkerError), + }); + } + }, [isResetPasswordWorkerError, resetPasswordWorkerError, showNotification]); + return ( - - {getErrorMessageForError(resetPasswordWorkerError)} - - ) : undefined - } - cancelNavigation={routerPaths.worker.profile} - showBackButton={false} - title={t('worker.resetPassword.title')} + - -
{ - void methods.handleSubmit(handleWorkerResetPassword)(event); + + - - - {t('worker.resetPassword.description')} - - - - - - - -
-
+ + + {t('worker.resetPassword.title')} + + + +
{ + void methods.handleSubmit(handleWorkerResetPassword)(event); + }} + > + + + {t('worker.resetPassword.description')} + + + + + + +
+
+ + ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/hooks/send-reset-link.ts b/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/hooks/send-reset-link.ts index e55ddb689f..c10b75c16f 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/hooks/send-reset-link.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/hooks/send-reset-link.ts @@ -1,12 +1,9 @@ -import { z } from 'zod'; import { useMutation } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; import { routerPaths } from '@/router/router-paths'; import { type SendResetLinkDto } from '../schemas'; import * as passwordService from '../../reset-password/password.service'; -export const SendResetLinkSuccessResponseSchema = z.unknown(); - export function useSendResetLinkMutation() { const navigate = useNavigate(); @@ -14,7 +11,7 @@ export function useSendResetLinkMutation() { mutationFn: async (data: SendResetLinkDto) => passwordService.sendResetLink(data), onSuccess: (_, { email }) => { - navigate(routerPaths.worker.sendResetLinkSuccess, { state: { email } }); + navigate(routerPaths.sendResetLinkSuccess, { state: { email } }); }, }); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/send-reset-link-success.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/send-reset-link-success.page.tsx index d379198a55..1144e9b7fa 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/send-reset-link-success.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/send-reset-link-success.page.tsx @@ -1,28 +1,31 @@ -import { Grid, Typography } from '@mui/material'; +import { useEffect } from 'react'; +import { Grid, Paper, Stack, Typography } from '@mui/material'; import { Trans, useTranslation } from 'react-i18next'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { FormProvider, useForm } from 'react-hook-form'; -import { PageCard } from '@/shared/components/ui/page-card'; import { Button } from '@/shared/components/ui/button'; import { useLocationState } from '@/modules/worker/hooks/use-location-state'; import { env } from '@/shared/env'; -import { Alert } from '@/shared/components/ui/alert'; import { getErrorMessageForError } from '@/shared/errors'; import { HCaptchaForm } from '@/shared/components/hcaptcha'; import { MailTo } from '@/shared/components/ui/mail-to'; import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; import { useColorMode } from '@/shared/contexts/color-mode'; -import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette'; import { useSendResetLinkMutation } from './hooks'; import { sendResetLinkHcaptchaDtoSchema, type SendResetLinkHcaptcha, } from './schemas'; +import { + TopNotificationType, + useNotification, +} from '@/shared/hooks/use-notification'; -export function SendResetLinkWorkerSuccessPage() { - const { colorPalette, isDarkMode } = useColorMode(); +export function SendResetLinkSuccessPage() { + const { colorPalette } = useColorMode(); const { t } = useTranslation(); + const { showNotification } = useNotification(); const { field: email } = useLocationState({ keyInStorage: 'email', schema: z.email(), @@ -43,79 +46,106 @@ export function SendResetLinkWorkerSuccessPage() { useResetMutationErrors(methods.watch, reset); + useEffect(() => { + if (isError) { + showNotification({ + type: TopNotificationType.ERROR, + message: getErrorMessageForError(error), + }); + } + }, [isError, error, showNotification]); + return ( - - {getErrorMessageForError(error)} - - ) : undefined - } - title={t('worker.sendResetLinkForm.title')} + - -
{ - void methods.handleSubmit(handleWorkerSendResetLink)(event); - }} + + - - - , - }} - i18nKey="worker.sendResetLinkSuccess.paragraph1" - values={{ email }} - /> - - - {t('worker.sendResetLinkSuccess.paragraph2')} - - - , - }} - i18nKey="worker.sendResetLinkSuccess.paragraph3" - values={{ email }} - /> - - - - - - , - 2: , + {t('worker.sendResetLinkForm.title')} + + + { + void methods.handleSubmit(handleWorkerSendResetLink)(event); + }} + > + + + , + }} + i18nKey="worker.sendResetLinkSuccess.paragraph1" + values={{ email }} + /> + + - - - - -
+ > + {t('worker.sendResetLinkSuccess.paragraph2')} + + + , + }} + i18nKey="worker.sendResetLinkSuccess.paragraph3" + values={{ email }} + /> + + + + + , + 2: , + }} + i18nKey="worker.sendResetLinkSuccess.paragraph4" + /> + + + + + + ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/send-reset-link.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/send-reset-link.page.tsx index 912399b95d..9fc49d96b3 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/send-reset-link.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/send-reset-link.page.tsx @@ -1,11 +1,10 @@ +import { useEffect } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; -import { Grid, Typography } from '@mui/material'; +import { Box, Grid, Paper, Stack, Typography } from '@mui/material'; import { zodResolver } from '@hookform/resolvers/zod'; import { useTranslation } from 'react-i18next'; -import { PageCard } from '@/shared/components/ui/page-card'; import { Input } from '@/shared/components/data-entry/input'; import { Button } from '@/shared/components/ui/button'; -import { Alert } from '@/shared/components/ui/alert'; import { getErrorMessageForError } from '@/shared/errors'; import { useAuth } from '@/modules/auth/hooks/use-auth'; import { HCaptchaForm } from '@/shared/components/hcaptcha'; @@ -13,10 +12,20 @@ import { routerPaths } from '@/router/router-paths'; import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; import { useSendResetLinkMutation } from './hooks'; import { type SendResetLinkDto, sendResetLinkDtoSchema } from './schemas'; +import { useColorMode } from '@/shared/contexts/color-mode'; +import { BackButton } from '@/shared/components/ui/page-card/back-button'; +import { useNavigate } from 'react-router-dom'; +import { + TopNotificationType, + useNotification, +} from '@/shared/hooks/use-notification'; -export function SendResetLinkWorkerPage() { +export function SendResetLinkPage() { const { t } = useTranslation(); const { user } = useAuth(); + const { colorPalette } = useColorMode(); + const { showNotification } = useNotification(); + const navigate = useNavigate(); const methods = useForm({ defaultValues: { @@ -27,61 +36,107 @@ export function SendResetLinkWorkerPage() { }); const { - mutate: sendResetLinkWorkerMutate, - error: sendResetLinkWorkerError, - isError: isSendResetLinkWorkerError, - isPending: isSendResetLinkWorkerPending, - reset: sendResetLinkWorkerMutateReset, + mutate: sendResetLinkMutate, + error: sendResetLinkError, + isError: isSendResetLinkError, + isPending: isSendResetLinkPending, + reset, } = useSendResetLinkMutation(); - useResetMutationErrors(methods.watch, sendResetLinkWorkerMutateReset); + useResetMutationErrors(methods.watch, reset); + + useEffect(() => { + if (isSendResetLinkError) { + const errorMessage = getErrorMessageForError(sendResetLinkError); + showNotification({ + type: TopNotificationType.ERROR, + message: errorMessage, + }); + } + }, [isSendResetLinkError, sendResetLinkError, showNotification]); function handleWorkerSendResetLink(data: SendResetLinkDto) { - sendResetLinkWorkerMutate(data); + sendResetLinkMutate(data); } + const handleBackButton = () => { + navigate(routerPaths.profile); + }; + return ( - - {getErrorMessageForError(sendResetLinkWorkerError)} - - ) : undefined - } - cancelNavigation={routerPaths.worker.profile} - title={t('worker.sendResetLinkForm.title')} + - -
{ - void methods.handleSubmit(handleWorkerSendResetLink)(event); + + - - - {t('worker.sendResetLinkForm.description')} - - - - - - -
-
+ + + {t('worker.sendResetLinkForm.title')} + + + +
{ + void methods.handleSubmit(handleWorkerSendResetLink)(event); + }} + > + + + {t('worker.sendResetLinkForm.description')} + + + + + +
+
+ + ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/exchangeApiKeys.service.ts b/packages/apps/human-app/frontend/src/modules/worker/services/exchangeApiKeys.service.ts deleted file mode 100644 index 41db9404e2..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/services/exchangeApiKeys.service.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { authorizedHumanAppApiClient } from '@/api'; - -interface ExchangeApiKey { - api_key: string; - exchange_name: string; -} - -interface Exchange { - name: string; - display_name: string; -} - -async function getSupportedExchanges(): Promise { - const response = await authorizedHumanAppApiClient.get( - '/exchange-api-keys/supported-exchanges' - ); - return response || []; -} - -async function getExchangeApiKeys(): Promise { - const response = - await authorizedHumanAppApiClient.get('/exchange-api-keys'); - return response || null; -} - -async function enrollExchangeApiKeys(data: { - exchange: string; - apiKey: string; - secretKey: string; -}): Promise { - const { exchange, ...body } = data; - await authorizedHumanAppApiClient.post(`/exchange-api-keys/${exchange}`, { - body, - }); -} - -async function deleteExchangeApiKeys(): Promise { - await authorizedHumanAppApiClient.delete('/exchange-api-keys'); -} - -export { - enrollExchangeApiKeys, - getExchangeApiKeys, - deleteExchangeApiKeys, - getSupportedExchanges, -}; diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/oracles.service.ts b/packages/apps/human-app/frontend/src/modules/worker/services/oracles.service.ts index 518006885e..bd4f5fd2b2 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/services/oracles.service.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/services/oracles.service.ts @@ -1,7 +1,6 @@ import { z } from 'zod'; import { ApiClientError, authorizedHumanAppApiClient } from '@/api'; import { env } from '@/shared/env'; -import { MainnetChains, TestnetChains } from '@/modules/smart-contracts/chains'; const apiPaths = { oracles: '/oracles', @@ -13,6 +12,10 @@ const OracleSchema = z.object({ role: z.string(), name: z.string(), url: z.string(), + nTasks: z.number(), + minRewardAmount: z.string(), + maxRewardAmount: z.string(), + rewardToken: z.string(), jobTypes: z.array(z.string()), registrationNeeded: z.boolean().optional().nullable(), registrationInstructions: z.string().optional().nullable(), @@ -26,41 +29,13 @@ export type Oracle = OracleBase & { name: string; }; -const isTestnet = env.VITE_NETWORK === 'testnet'; - -const H_CAPTCHA_ORACLE: Oracle = { - address: env.VITE_H_CAPTCHA_ORACLE_ADDRESS, - chainId: isTestnet ? TestnetChains[0].chainId : MainnetChains[0].chainId, - jobTypes: env.VITE_H_CAPTCHA_ORACLE_TASK_TYPES, - role: env.VITE_H_CAPTCHA_ORACLE_ROLE, - url: env.VITE_H_CAPTCHA_ORACLE_ANNOTATION_TOOL, - name: 'hCaptcha', - registrationNeeded: false, -}; - -async function getOracles(selectedJobTypes: string[]) { +async function getOracles() { try { - const params = selectedJobTypes.length - ? { selected_job_types: selectedJobTypes } - : undefined; - - const queryParams = params ?? {}; - let oracles: Oracle[] = []; - if ( - selectedJobTypes.length === 0 || - selectedJobTypes.some((t) => H_CAPTCHA_ORACLE.jobTypes.includes(t)) - ) { - oracles.push(H_CAPTCHA_ORACLE); - } - if (env.VITE_FEATURE_FLAG_JOBS_DISCOVERY) { const results = await authorizedHumanAppApiClient.get( - apiPaths.oracles, - { - queryParams, - } + apiPaths.oracles ); if (Array.isArray(results)) { diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/staking.service.ts b/packages/apps/human-app/frontend/src/modules/worker/services/staking.service.ts deleted file mode 100644 index d4a38a65b3..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/services/staking.service.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { authorizedHumanAppApiClient } from '@/api'; - -interface StakeSummary { - exchange_stake: string; - exchange_error?: string; - on_chain_stake: string; - on_chain_error?: string; -} - -async function getStakingSummary(): Promise { - const response = - await authorizedHumanAppApiClient.get('/staking/summary'); - return response || null; -} - -export { getStakingSummary }; diff --git a/packages/apps/human-app/frontend/src/router/components/drawer-menu-items/drawer-menu-items-operator.tsx b/packages/apps/human-app/frontend/src/router/components/drawer-menu-items/drawer-menu-items-operator.tsx deleted file mode 100644 index 0b12d28763..0000000000 --- a/packages/apps/human-app/frontend/src/router/components/drawer-menu-items/drawer-menu-items-operator.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { t } from 'i18next'; -import { HelpIcon, UserOutlinedIcon } from '@/shared/components/ui/icons'; -import { routerPaths } from '@/router/router-paths'; -import { DarkModeSwitch } from '@/shared/components/ui/dark-mode-switch'; -import { type MenuItem } from '../layout/protected'; - -export const operatorDrawerBottomMenuItems: MenuItem[] = [ - { - label: t('components.DrawerNavigation.profile'), - link: routerPaths.operator.profile, - icon: , - }, - { - label: t('components.DrawerNavigation.help'), - icon: , - onClick: () => { - // @ts-expect-error -- ... - if ($zoho?.salesiq?.chat?.start) { - // @ts-expect-error -- ... - $zoho.salesiq.chat.start(); - } - }, - }, - , -]; diff --git a/packages/apps/human-app/frontend/src/router/components/drawer-menu-items/drawer-menu-items-worker.tsx b/packages/apps/human-app/frontend/src/router/components/drawer-menu-items/drawer-menu-items-worker.tsx deleted file mode 100644 index 5c39359e16..0000000000 --- a/packages/apps/human-app/frontend/src/router/components/drawer-menu-items/drawer-menu-items-worker.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { t } from 'i18next'; -import { - HelpIcon, - UserOutlinedIcon, - WorkIcon, -} from '@/shared/components/ui/icons'; -import { DarkModeSwitch } from '@/shared/components/ui/dark-mode-switch'; -import type { UserData } from '@/modules/auth/context/auth-context'; -import { routerPaths } from '@/router/router-paths'; -import { KycStatus } from '@/modules/worker/profile/types'; -import { type MenuItem } from '../layout/protected'; -import { UiConfig } from '@/shared/services/ui-config.service'; - -export const workerDrawerTopMenuItems = ( - user: UserData | null, - uiConfig: UiConfig | undefined -): MenuItem[] => { - return [ - { - label: t('components.DrawerNavigation.jobs'), - icon: , - link: routerPaths.worker.jobsDiscovery, - disabled: - !user?.wallet_address || - (uiConfig?.stakingEligibilityEnabled && !user?.is_stake_eligible) || - user.kyc_status !== KycStatus.APPROVED, - }, - ]; -}; - -export const workerDrawerBottomMenuItems: MenuItem[] = [ - { - label: t('components.DrawerNavigation.profile'), - link: routerPaths.worker.profile, - icon: , - }, - { - label: t('components.DrawerNavigation.help'), - icon: , - onClick: () => { - // @ts-expect-error -- ... - if ($zoho?.salesiq?.chat?.start) { - // @ts-expect-error -- ... - $zoho.salesiq.chat.start(); - } - }, - }, - , -]; diff --git a/packages/apps/human-app/frontend/src/router/components/drawer-menu-items/index.ts b/packages/apps/human-app/frontend/src/router/components/drawer-menu-items/index.ts deleted file mode 100644 index 26df1d0e31..0000000000 --- a/packages/apps/human-app/frontend/src/router/components/drawer-menu-items/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './drawer-menu-items-operator'; -export * from './drawer-menu-items-worker'; diff --git a/packages/apps/human-app/frontend/src/router/components/footer.tsx b/packages/apps/human-app/frontend/src/router/components/footer.tsx index 25e00a70f5..79ae8ef89e 100644 --- a/packages/apps/human-app/frontend/src/router/components/footer.tsx +++ b/packages/apps/human-app/frontend/src/router/components/footer.tsx @@ -1,127 +1,92 @@ import { Grid, Link, Stack, Typography } from '@mui/material'; import { useTranslation } from 'react-i18next'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; import { env } from '@/shared/env'; import { Chat } from '@/modules/homepage/components/chat'; -import { breakpoints } from '@/shared/styles/breakpoints'; import { useColorMode } from '@/shared/contexts/color-mode'; interface FooterProps { displayChatIcon?: boolean; - isProtected?: boolean; } -export function Footer({ - isProtected, - displayChatIcon = true, -}: Readonly) { - const { colorPalette } = useColorMode(); + +export function Footer({ displayChatIcon = true }: FooterProps) { + const { colorPalette, isDarkMode } = useColorMode(); const { t } = useTranslation(); - const isMobile = useIsMobile('md'); - const getLeftPadding = () => { - if (isMobile) { - return '0'; - } - if (isProtected) { - return '200px'; - } - return '0'; - }; + const footerTextColor = isDarkMode ? 'rgba(255, 255, 255, 0.70)' : '#676767'; return ( - - {t('components.footer.privacyPolicy')} - + {t('components.footer.privacyPolicy')} - - {t('components.footer.termsOfService')} - + {t('components.footer.termsOfService')} - - {t('components.footer.humanProtocol')} - + {t('components.footer.humanProtocol')} - {isMobile ? ( - - {t('components.footer.copyrightNote')} - - ) : null} - {!isMobile ? ( - - {t('components.footer.copyrightNote')} - - ) : null} - - - + + {t('components.footer.copyrightNote')} + + ); } diff --git a/packages/apps/human-app/frontend/src/router/components/index.ts b/packages/apps/human-app/frontend/src/router/components/index.ts index 0a514e542c..c78a32e4dc 100644 --- a/packages/apps/human-app/frontend/src/router/components/index.ts +++ b/packages/apps/human-app/frontend/src/router/components/index.ts @@ -1,3 +1,2 @@ -export * from './drawer-menu-items'; export * from './layout/protected'; export * from './layout/unprotected'; diff --git a/packages/apps/human-app/frontend/src/router/components/layout/helpers/index.ts b/packages/apps/human-app/frontend/src/router/components/layout/helpers/index.ts deleted file mode 100644 index c7582d2d26..0000000000 --- a/packages/apps/human-app/frontend/src/router/components/layout/helpers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './is-drawer-item'; diff --git a/packages/apps/human-app/frontend/src/router/components/layout/helpers/is-drawer-item.ts b/packages/apps/human-app/frontend/src/router/components/layout/helpers/is-drawer-item.ts deleted file mode 100644 index fda999eea3..0000000000 --- a/packages/apps/human-app/frontend/src/router/components/layout/helpers/is-drawer-item.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { type MenuItem, type DrawerItem } from '../protected'; - -export function isDrawerItem(item: MenuItem): item is DrawerItem { - return 'label' in item; -} diff --git a/packages/apps/human-app/frontend/src/router/components/layout/protected/bottom-menu-items-list.tsx b/packages/apps/human-app/frontend/src/router/components/layout/protected/bottom-menu-items-list.tsx deleted file mode 100644 index 8bf0bafb4d..0000000000 --- a/packages/apps/human-app/frontend/src/router/components/layout/protected/bottom-menu-items-list.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { - List, - ListItem, - Stack, - ListItemButton, - ListItemText, - Typography, -} from '@mui/material'; -import { useLocation } from 'react-router-dom'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { useIsMobile } from '@/shared/hooks'; -import { colorPalette } from '@/shared/styles/color-palette'; -import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette'; -import { isDrawerItem } from '../helpers'; -import { type MenuItem, type DrawerItem } from './drawer-navigation'; - -interface MenuItemListProps { - handleItemClick: (item: DrawerItem) => void; - items?: MenuItem[]; -} -export function BottomMenuItemsList({ - handleItemClick, - items = [], -}: Readonly) { - const { isDarkMode } = useColorMode(); - const isMobile = useIsMobile(); - const location = useLocation(); - - return ( - - {items.map((item) => { - if (!isDrawerItem(item)) { - return ( - - - {item} - - - ); - } - - const { label, link, icon } = item; - const isActive = location.pathname === link; - - return ( - - { - handleItemClick(item); - }} - selected={isActive} - sx={{ - borderRadius: '4px', - p: 0, - '&.Mui-selected': { - backgroundColor: isDarkMode - ? onlyDarkModeColor.listItemColor - : colorPalette.primary.shades, - }, - }} - > - - {icon} - - {label} - - } - sx={{ - textAlign: 'center', - marginLeft: '10px', - }} - /> - - - - ); - })} - - ); -} diff --git a/packages/apps/human-app/frontend/src/router/components/layout/protected/desktop-aside-bar.tsx b/packages/apps/human-app/frontend/src/router/components/layout/protected/desktop-aside-bar.tsx new file mode 100644 index 0000000000..0c93bde29b --- /dev/null +++ b/packages/apps/human-app/frontend/src/router/components/layout/protected/desktop-aside-bar.tsx @@ -0,0 +1,177 @@ +import { + Box, + IconButton, + List, + ListItem, + ListItemButton, + Stack, +} from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { Link, useLocation } from 'react-router-dom'; + +import { ProfileData } from '@/modules/worker/profile/components/profile-data'; +import { routerPaths } from '@/router/router-paths'; +import { Button } from '@/shared/components/ui/button'; +import { ColorModeSwitch } from '@/shared/components/ui/dark-mode-switch'; +import { + HelpIcon, + HumanLogoNavbarIcon, + TriangleIcon, +} from '@/shared/components/ui/icons'; +import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode'; +import { useHandleMainNavIconClick } from '@/shared/hooks/use-handle-main-nav-icon-click'; + +const menuItems = [ + { + labelKey: 'components.DrawerNavigation.availableJobs', + href: routerPaths.jobsDiscovery, + }, + { + labelKey: 'components.DrawerNavigation.myJobs', + href: routerPaths.myJobs, + }, +] as const; + +export function DesktopAsideBar() { + const { colorPalette, isDarkMode } = useColorMode(); + const { t } = useTranslation(); + const { pathname } = useLocation(); + const handleMainNavIconClick = useHandleMainNavIconClick(); + + const activeLinkGradient = isDarkMode + ? 'linear-gradient(90deg, rgba(212, 207, 255, 0.10) 0%, rgba(37, 29, 71, 0.10) 90%)' + : 'linear-gradient(90deg, rgba(50, 10, 141, 0.10) 0%, rgba(255, 255, 255, 0.10) 90%)'; + + return ( + + + + + + + + + + {menuItems.map((item) => { + const { href, labelKey } = item; + const isActive = pathname === href; + return ( + + + {isActive && } + {t(labelKey)} + + + ); + })} + + + + + + + + + + + + + + + + + + ); +} diff --git a/packages/apps/human-app/frontend/src/router/components/layout/protected/drawer-navigation.tsx b/packages/apps/human-app/frontend/src/router/components/layout/protected/drawer-navigation.tsx deleted file mode 100644 index ac6d46a6b5..0000000000 --- a/packages/apps/human-app/frontend/src/router/components/layout/protected/drawer-navigation.tsx +++ /dev/null @@ -1,130 +0,0 @@ -import Box from '@mui/material/Box'; -import Drawer from '@mui/material/Drawer'; -import CssBaseline from '@mui/material/CssBaseline'; -import { Stack } from '@mui/material'; -import { useNavigate } from 'react-router-dom'; -import { t } from 'i18next'; -import type { Dispatch, ReactElement, SetStateAction } from 'react'; -import { HumanLogoNavbarIcon } from '@/shared/components/ui/icons'; -import { Button } from '@/shared/components/ui/button'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { useHandleMainNavIconClick } from '@/shared/hooks/use-handle-main-nav-icon-click'; -import { TopMenuItemsList } from './top-menu-items-list'; -import { BottomMenuItemsList } from './bottom-menu-items-list'; - -const drawerWidth = 240; - -export interface DrawerItem { - label: string; - link?: string; - href?: string; - icon?: ReactElement; - disabled?: boolean; - onClick?: () => void; -} - -export type MenuItem = DrawerItem | ReactElement; -interface DrawerNavigationProps { - open: boolean; - setDrawerOpen: Dispatch>; - topMenuItems?: MenuItem[]; - bottomMenuItems?: MenuItem[]; - signOut: () => void; -} - -export function DrawerNavigation({ - open, - setDrawerOpen, - topMenuItems, - bottomMenuItems, - signOut, -}: Readonly) { - const navigate = useNavigate(); - const isMobile = useIsMobile(); - const handleMainNavIconClick = useHandleMainNavIconClick(); - - const handleItemClick = ({ disabled, href, link, onClick }: DrawerItem) => { - if (disabled) return; - - if (isMobile) setDrawerOpen(false); - - if (onClick) { - onClick(); - return; - } - - if (href) { - window.open(href, '_blank', 'noreferrer'); - return; - } - - if (link) { - navigate(link); - } - }; - - return ( - - - - {!isMobile && ( - { - handleMainNavIconClick(); - }} - > - - - )} - - - - - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/router/components/layout/protected/index.ts b/packages/apps/human-app/frontend/src/router/components/layout/protected/index.ts index 8643262e0f..5d15fe1b3c 100644 --- a/packages/apps/human-app/frontend/src/router/components/layout/protected/index.ts +++ b/packages/apps/human-app/frontend/src/router/components/layout/protected/index.ts @@ -1,2 +1 @@ export * from './layout'; -export * from './drawer-navigation'; diff --git a/packages/apps/human-app/frontend/src/router/components/layout/protected/jobs-switcher-mobile.tsx b/packages/apps/human-app/frontend/src/router/components/layout/protected/jobs-switcher-mobile.tsx new file mode 100644 index 0000000000..675b05b234 --- /dev/null +++ b/packages/apps/human-app/frontend/src/router/components/layout/protected/jobs-switcher-mobile.tsx @@ -0,0 +1,66 @@ +import { routerPaths } from '@/router/router-paths'; +import { Button } from '@/shared/components/ui/button'; +import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode'; +import { Stack } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { Link, useLocation } from 'react-router-dom'; + +const BUTTONS = [ + { + label: 'worker.jobs.availableJobs', + path: routerPaths.jobsDiscovery, + }, + { + label: 'worker.jobs.myJobs', + path: routerPaths.myJobs, + }, +] as const satisfies { label: string; path: string }[]; + +export function JobsSwitcherMobile() { + const { colorPalette, isDarkMode } = useColorMode(); + const { t } = useTranslation(); + const { pathname } = useLocation(); + + const shadowColor = isDarkMode + ? 'rgba(255, 255, 255, 0.05)' + : 'rgba(0, 0, 0, 0.1)'; + + return ( + + {BUTTONS.map((button) => { + const isActive = pathname === button.path; + return ( + + ); + })} + + ); +} diff --git a/packages/apps/human-app/frontend/src/router/components/layout/protected/layout.tsx b/packages/apps/human-app/frontend/src/router/components/layout/protected/layout.tsx index ebe1e2ead6..af0bb348c3 100644 --- a/packages/apps/human-app/frontend/src/router/components/layout/protected/layout.tsx +++ b/packages/apps/human-app/frontend/src/router/components/layout/protected/layout.tsx @@ -1,134 +1,81 @@ -import { Grid, styled } from '@mui/material'; -import type { Dispatch, ReactElement, SetStateAction } from 'react'; -import { useEffect, useRef, useState } from 'react'; -import { Outlet } from 'react-router-dom'; +import { useRef } from 'react'; +import { Outlet, useLocation } from 'react-router-dom'; +import { Box, Stack, styled } from '@mui/material'; + import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { useBackgroundContext } from '@/shared/contexts/background'; -import { breakpoints } from '@/shared/styles/breakpoints'; -import { useIsHCaptchaLabelingPage } from '@/shared/hooks/use-is-hcaptcha-labeling-page'; import { GovernanceBanner } from '@/modules/governance-banner/components/governance-banner'; import { Footer } from '../../footer'; import { Navbar } from './navbar'; -import { type PageHeaderProps, PageHeader } from './page-header'; +import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode'; +import { DesktopAsideBar } from './desktop-aside-bar'; +import { ProfileBottomTray } from '@/modules/worker/profile/components/profile-bottom-tray'; +import { MOBILE_BOTTOM_TRAY_HEIGHT } from '@/shared/consts'; +import { routerPaths } from '@/router/router-paths'; -const Main = styled('main', { - shouldForwardProp: (prop) => prop !== 'open' && prop !== 'isMobile', -})<{ - open?: boolean; - isMobile?: boolean; -}>(({ theme, open, isMobile }) => ({ - width: '100%', +const Main = styled('main')({ display: 'flex', flex: '1', - transition: theme.transitions.create('margin', { - easing: theme.transitions.easing.sharp, - duration: theme.transitions.duration.leavingScreen, - }), - ...(open && { - transition: theme.transitions.create('margin', { - easing: theme.transitions.easing.easeOut, - duration: theme.transitions.duration.enteringScreen, - }), - paddingLeft: isMobile ? 0 : `140px`, - }), -})); + width: '100%', +}); -export function ProtectedLayout({ - pageHeaderProps, - renderDrawer, - renderHCaptchaStatisticsDrawer, - renderGovernanceBanner, -}: Readonly<{ - pageHeaderProps: PageHeaderProps; - renderDrawer: ( - open: boolean, - setDrawerOpen: Dispatch> - ) => ReactElement; - renderHCaptchaStatisticsDrawer?: (isOpen: boolean) => ReactElement; - renderGovernanceBanner?: boolean; -}>) { +export function ProtectedLayout() { const layoutElementRef = useRef(null); - const isHCaptchaLabelingPage = useIsHCaptchaLabelingPage(); - const isMobile = useIsMobile(); - const [drawerOpen, setDrawerOpen] = useState(!isMobile); - const [hcaptchaDrawerOpen, setHcaptchaDrawerOpen] = useState(false); - const { backgroundColor, setGrayBackground } = useBackgroundContext(); - const toggleUserStatsDrawer = isHCaptchaLabelingPage - ? () => { - setHcaptchaDrawerOpen((state) => !state); - } - : undefined; - useEffect(() => { - if (isMobile) { - setHcaptchaDrawerOpen(false); - setDrawerOpen(false); - } else { - setHcaptchaDrawerOpen(false); - setDrawerOpen(true); - } - }, [isMobile]); + const isMobile = useIsMobile(); + const { colorPalette } = useColorMode(); + const location = useLocation(); - useEffect(() => { - setGrayBackground(); - }, [setGrayBackground]); + const isProfilePage = location.pathname === routerPaths.profile; + const isBottomTrayVisible = isMobile && !isProfilePage; return ( - - - {renderDrawer(drawerOpen, setDrawerOpen)} - {isHCaptchaLabelingPage && renderHCaptchaStatisticsDrawer - ? renderHCaptchaStatisticsDrawer(hcaptchaDrawerOpen) - : null} -
- - {renderGovernanceBanner && } - - - - - - - -
-