diff --git a/.github/workflows/cd-subgraph.yaml b/.github/workflows/cd-subgraph.yaml index caa0051435..271dc42ad9 100644 --- a/.github/workflows/cd-subgraph.yaml +++ b/.github/workflows/cd-subgraph.yaml @@ -27,8 +27,8 @@ jobs: max-parallel: 3 steps: - uses: actions/checkout@v4 - - name: Set up Node.js - uses: actions/setup-node@v4 + - uses: actions/setup-node@v4 + name: Set up Node.js with: node-version-file: .nvmrc - name: Filter Networks diff --git a/.github/workflows/ci-test-subgraph.yaml b/.github/workflows/ci-test-subgraph.yaml index 700ed3db95..30f369d90c 100644 --- a/.github/workflows/ci-test-subgraph.yaml +++ b/.github/workflows/ci-test-subgraph.yaml @@ -17,7 +17,7 @@ jobs: runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 + - uses: actions/setup-node@v4 with: node-version-file: .nvmrc - run: npm install --global yarn && yarn diff --git a/packages/apps/dashboard/client/package.json b/packages/apps/dashboard/client/package.json index b926e29535..082facf182 100644 --- a/packages/apps/dashboard/client/package.json +++ b/packages/apps/dashboard/client/package.json @@ -21,7 +21,7 @@ "@mui/styled-engine-sc": "6.4.0", "@mui/x-data-grid": "^7.23.2", "@mui/x-date-pickers": "^7.23.6", - "@tanstack/react-query": "^5.48.0", + "@tanstack/react-query": "^5.67.2", "@types/react-router-dom": "^5.3.3", "@types/recharts": "^1.8.29", "axios": "^1.7.2", diff --git a/packages/apps/fortune/exchange-oracle/client/package.json b/packages/apps/fortune/exchange-oracle/client/package.json index c85013f5ee..35712e7cee 100644 --- a/packages/apps/fortune/exchange-oracle/client/package.json +++ b/packages/apps/fortune/exchange-oracle/client/package.json @@ -27,9 +27,9 @@ "dependencies": { "@human-protocol/sdk": "*", "@mui/material": "^5.16.7", - "@tanstack/query-sync-storage-persister": "^5.59.0", - "@tanstack/react-query": "^5.60.5", - "@tanstack/react-query-persist-client": "^5.66.9", + "@tanstack/query-sync-storage-persister": "^5.68.0", + "@tanstack/react-query": "^5.67.2", + "@tanstack/react-query-persist-client": "^5.67.2", "axios": "^1.7.2", "ethers": "^6.13.5", "react": "^18.3.1", diff --git a/packages/apps/human-app/frontend/package.json b/packages/apps/human-app/frontend/package.json index ceefa99ef4..a1209b1519 100644 --- a/packages/apps/human-app/frontend/package.json +++ b/packages/apps/human-app/frontend/package.json @@ -26,7 +26,7 @@ "@reown/appkit": "1.3.2", "@reown/appkit-adapter-wagmi": "1.3.2", "@synaps-io/verify-sdk": "^4.0.45", - "@tanstack/react-query": "^5.61.0", + "@tanstack/react-query": "^5.67.2", "date-fns": "^4.1.0", "ethers": "^6.13.5", "i18next": "^23.8.2", diff --git a/packages/apps/human-app/frontend/src/api/fetch-refresh-token.ts b/packages/apps/human-app/frontend/src/api/fetch-refresh-token.ts index 77226af471..2f99243801 100644 --- a/packages/apps/human-app/frontend/src/api/fetch-refresh-token.ts +++ b/packages/apps/human-app/frontend/src/api/fetch-refresh-token.ts @@ -1,6 +1,6 @@ import { apiPaths } from '@/api/api-paths'; import { browserAuthProvider } from '@/shared/contexts/browser-auth-provider'; -import { signInSuccessResponseSchema } from '@/modules/worker/services/sign-in/schema'; +import { authTokensSuccessResponseSchema } from '@/shared/schemas'; export const fetchTokenRefresh = async (baseUrl: string) => { const response = await fetch( @@ -23,7 +23,7 @@ export const fetchTokenRefresh = async (baseUrl: string) => { const data: unknown = await response.json(); - const refetchAccessTokenSuccess = signInSuccessResponseSchema.parse(data); + const refetchAccessTokenSuccess = authTokensSuccessResponseSchema.parse(data); return refetchAccessTokenSuccess; }; diff --git a/packages/apps/human-app/frontend/src/api/fetcher.ts b/packages/apps/human-app/frontend/src/api/fetcher.ts index d91945c258..275a9932e3 100644 --- a/packages/apps/human-app/frontend/src/api/fetcher.ts +++ b/packages/apps/human-app/frontend/src/api/fetcher.ts @@ -3,8 +3,8 @@ import { ZodError, type ZodType, type ZodTypeDef } from 'zod'; import type { ResponseError } from '@/shared/types/global.type'; import { browserAuthProvider } from '@/shared/contexts/browser-auth-provider'; import { env } from '@/shared/env'; -import { type SignInSuccessResponse } from '@/modules/worker/services/sign-in/types'; import { normalizeBaseUrl } from '@/shared/helpers/url'; +import { type AuthTokensSuccessResponse } from '@/shared/schemas'; import { fetchTokenRefresh } from './fetch-refresh-token'; const appendHeader = ( @@ -66,7 +66,7 @@ export type FetcherOptions = export type FetcherUrl = string | URL; -let refreshPromise: Promise | null = null; +let refreshPromise: Promise | null = null; export async function refreshToken(): Promise<{ access_token: string; 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 index 4f1f1f2f6c..a3332c8a09 100644 --- 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 @@ -3,12 +3,12 @@ import { useState, createContext, useEffect } from 'react'; import { jwtDecode } from 'jwt-decode'; import { z } from 'zod'; import { useQueryClient } from '@tanstack/react-query'; -import type { SignInSuccessResponse } from '@/modules/worker/services/sign-in/types'; import { browserAuthProvider } from '@/shared/contexts/browser-auth-provider'; import { ModalType, useModalStore, } from '@/shared/components/ui/modal/modal.store'; +import { type AuthTokensSuccessResponse } from '@/shared/schemas'; const web3userDataSchema = z.object({ userId: z.number(), @@ -25,7 +25,7 @@ export interface Web3AuthenticatedUserContextType { user: Web3UserData; status: AuthStatus; signOut: (throwExpirationModal?: boolean) => void; - signIn: (singIsSuccess: SignInSuccessResponse) => void; + signIn: (singIsSuccess: AuthTokensSuccessResponse) => void; updateUserData: (updateUserDataPayload: Partial) => void; } @@ -33,7 +33,7 @@ interface Web3UnauthenticatedUserContextType { user: null; status: AuthStatus; signOut: (throwExpirationModal?: boolean) => void; - signIn: (singIsSuccess: SignInSuccessResponse) => void; + signIn: (singIsSuccess: AuthTokensSuccessResponse) => void; } export const Web3AuthContext = createContext< @@ -97,7 +97,7 @@ export function Web3AuthProvider({ children }: { children: React.ReactNode }) { } }; - const signIn = (singIsSuccess: SignInSuccessResponse) => { + const signIn = (singIsSuccess: AuthTokensSuccessResponse) => { browserAuthProvider.signIn(singIsSuccess, 'web3'); handleSignIn(); }; 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 b98a797e8b..ed300db26e 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 @@ -3,12 +3,12 @@ import { useState, createContext, useEffect } from 'react'; import { jwtDecode } from 'jwt-decode'; import { z } from 'zod'; import { useQueryClient } from '@tanstack/react-query'; -import type { SignInSuccessResponse } from '@/modules/worker/services/sign-in/types'; import { browserAuthProvider } from '@/shared/contexts/browser-auth-provider'; import { ModalType, useModalStore, } from '@/shared/components/ui/modal/modal.store'; +import { type AuthTokensSuccessResponse } from '@/shared/schemas'; const extendableUserDataSchema = z.object({ site_key: z.string().optional().nullable(), @@ -35,7 +35,7 @@ export interface AuthenticatedUserContextType { user: UserData; status: AuthStatus; signOut: (throwExpirationModal?: boolean) => void; - signIn: (singIsSuccess: SignInSuccessResponse) => void; + signIn: (singIsSuccess: AuthTokensSuccessResponse) => void; updateUserData: (updateUserDataPayload: UpdateUserDataPayload) => void; } @@ -43,7 +43,7 @@ interface UnauthenticatedUserContextType { user: null; status: AuthStatus; signOut: (throwExpirationModal?: boolean) => void; - signIn: (singIsSuccess: SignInSuccessResponse) => void; + signIn: (singIsSuccess: AuthTokensSuccessResponse) => void; } export const AuthContext = createContext< @@ -112,7 +112,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { } }; - const signIn = (singIsSuccess: SignInSuccessResponse) => { + const signIn = (singIsSuccess: AuthTokensSuccessResponse) => { browserAuthProvider.signIn(singIsSuccess, 'web2'); handleSignIn(); }; 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 index c8bab39a3b..8a804abdf7 100644 --- a/packages/apps/human-app/frontend/src/modules/homepage/components/index.ts +++ b/packages/apps/human-app/frontend/src/modules/homepage/components/index.ts @@ -1,3 +1 @@ export * from './home-container'; -export * from './welcome'; -export * from './choose-sign-up-account-type'; 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 new file mode 100644 index 0000000000..6116bb99a3 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/homepage/components/logo-section.tsx @@ -0,0 +1,72 @@ +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 index 4486365bbd..92d38e947b 100644 --- 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 @@ -4,11 +4,11 @@ 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 { useWeb3SignIn } from '@/modules/operator/hooks/use-web3-signin'; import { useWeb3Auth } from '@/modules/auth-web3/hooks/use-web3-auth'; import { routerPaths } from '@/router/router-paths'; import { getErrorMessageForError } from '@/shared/errors'; import { PrepareSignatureType } from '@/api/hooks/use-prepare-signature'; +import { useWeb3SignIn } from '../hooks'; export function OperatorSignIn() { const { isConnected, openModal, address } = useWalletConnect(); 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 new file mode 100644 index 0000000000..0c33122c08 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/homepage/components/sign-in-section.tsx @@ -0,0 +1,42 @@ +import { Paper, Button, Divider } from '@mui/material'; +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'; + +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 index 23ad2f1c66..2737034284 100644 --- a/packages/apps/human-app/frontend/src/modules/homepage/components/welcome.tsx +++ b/packages/apps/human-app/frontend/src/modules/homepage/components/welcome.tsx @@ -1,138 +1,33 @@ -import { Divider, Grid, Paper, Stack, Typography } from '@mui/material'; -import { useTranslation } from 'react-i18next'; +import { Grid } from '@mui/material'; import { useEffect } from 'react'; -import { - HomepageLogoIcon, - HomepageUserIcon, - HomepageWorkIcon, - MobileHomeIcons, -} from '@/shared/components/ui/icons'; -import { Button } from '@/shared/components/ui/button'; import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { WorkerSignIn } from '@/modules/homepage/components/worker-sign-in'; import { useColorMode } from '@/shared/contexts/color-mode'; -import { useHomePageState } from '@/shared/contexts/homepage-state'; import { useBackgroundContext } from '@/shared/contexts/background'; -import { OperatorSignIn } from './operator-sign-in'; +import { SignInSection } from './sign-in-section'; +import { LogoSection } from './logo-section'; export function Welcome() { - const { colorPalette, isDarkMode } = useColorMode(); + const { isDarkMode } = useColorMode(); const { setWhiteBackground } = useBackgroundContext(); - const { setPageView } = useHomePageState(); - const { t } = useTranslation(); - const logoText: string = t('homepage.humanApp'); - const logoTextSplit: string[] = logoText.split(' '); const isMobile = useIsMobile('lg'); useEffect(() => { if (!isDarkMode) { setWhiteBackground(); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [isDarkMode, setWhiteBackground]); return ( - - {isMobile ? ( - - - - ) : ( - - - - - - - - - - - - )} - - {logoTextSplit[0]} - - {logoTextSplit[1]} - - - - {t('homepage.completeJobs')} - - + - - - - - - + ); 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 new file mode 100644 index 0000000000..f8ca47e636 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/homepage/hooks/index.ts @@ -0,0 +1 @@ +export * from './use-web3-signin'; diff --git a/packages/apps/human-app/frontend/src/modules/operator/hooks/use-web3-signin.ts b/packages/apps/human-app/frontend/src/modules/homepage/hooks/use-web3-signin.ts similarity index 100% rename from packages/apps/human-app/frontend/src/modules/operator/hooks/use-web3-signin.ts rename to packages/apps/human-app/frontend/src/modules/homepage/hooks/use-web3-signin.ts 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 index 72939ae5ae..6a0e8ed3e5 100644 --- a/packages/apps/human-app/frontend/src/modules/operator/hooks/index.ts +++ b/packages/apps/human-app/frontend/src/modules/operator/hooks/index.ts @@ -1,3 +1 @@ -export * from './use-disable-operator'; export * from './use-get-keys'; -export * from './use-web3-signin'; 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 index dbcc41c656..b52f63e631 100644 --- 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 @@ -1,2 +1,4 @@ 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 new file mode 100644 index 0000000000..ef8c77352a --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/operator/profile/components/operator-info.tsx @@ -0,0 +1,127 @@ +import { Paper, Typography, Stack, List, Grid } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +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.status === '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 new file mode 100644 index 0000000000..729eabc9f6 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/operator/profile/components/operator-stats.tsx @@ -0,0 +1,75 @@ +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 '../hooks'; + +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 index e8b89fcc4f..5cab8f9707 100644 --- 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 @@ -1,6 +1,5 @@ import { t } from 'i18next'; import { Typography } from '@mui/material'; -import { useDisableWeb3Operator } from '@/modules/operator/hooks/use-disable-operator'; import { useConnectedWallet } from '@/shared/contexts/wallet-connect'; import { Button } from '@/shared/components/ui/button'; import type { SignatureData } from '@/api/hooks/use-prepare-signature'; @@ -8,6 +7,7 @@ import { PrepareSignatureType, usePrepareSignature, } from '@/api/hooks/use-prepare-signature'; +import { useDisableWeb3Operator } from '../hooks'; export function ProfileDisableButton() { const { address, signMessage } = useConnectedWallet(); 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 index d67ff6f5e3..033ed47173 100644 --- 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 @@ -1 +1,2 @@ export * from './use-get-stats'; +export * from './use-disable-operator'; diff --git a/packages/apps/human-app/frontend/src/modules/operator/hooks/use-disable-operator.ts b/packages/apps/human-app/frontend/src/modules/operator/profile/hooks/use-disable-operator.ts similarity index 100% rename from packages/apps/human-app/frontend/src/modules/operator/hooks/use-disable-operator.ts rename to packages/apps/human-app/frontend/src/modules/operator/profile/hooks/use-disable-operator.ts 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 index 64b186089d..8bc801b898 100644 --- 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 @@ -14,10 +14,6 @@ const operatorStatsSuccessResponseSchema = z.object({ escrows_cancelled: z.number(), }); -export type OperatorStatsSuccessResponse = z.infer< - typeof operatorStatsSuccessResponseSchema ->; - const failedResponse = { workers_total: '-', assignments_completed: '-', @@ -28,6 +24,16 @@ const failedResponse = { escrows_cancelled: '-', }; +type OperatorStatsSuccessResponse = z.infer< + typeof operatorStatsSuccessResponseSchema +>; + +type OperatorStatsFailedResponse = typeof failedResponse; + +export type OperatorStatsResponse = + | OperatorStatsSuccessResponse + | OperatorStatsFailedResponse; + export function useGetOperatorStats() { const { data: keysData } = useGetKeys(); 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 index a65e3d487b..6e0e610130 100644 --- 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 @@ -1,25 +1,17 @@ import { useEffect } from 'react'; -import { Grid, List, Paper, Stack, Typography } from '@mui/material'; -import { useTranslation } from 'react-i18next'; +import { Grid } from '@mui/material'; import { useIsMobile } from '@/shared/hooks/use-is-mobile'; import { useGetKeys } from '@/modules/operator/hooks/use-get-keys'; -import { useWeb3AuthenticatedUser } from '@/modules/auth-web3/hooks/use-web3-authenticated-user'; import { PageCardError, PageCardLoader, } from '@/shared/components/ui/page-card'; import { getErrorMessageForError } from '@/shared/errors'; -import { CheckmarkIcon, LockerIcon } from '@/shared/components/ui/icons'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { ProfileListItem } from '@/shared/components/ui/profile'; import { useGetOperatorStats } from './hooks'; -import { ProfileDisableButton, ProfileEnableButton } from './components'; +import { OperatorInfo, OperatorStats } from './components'; export function OperatorProfilePage() { - const { colorPalette } = useColorMode(); - const { t } = useTranslation(); const isMobile = useIsMobile('lg'); - const { user } = useWeb3AuthenticatedUser(); const { data: keysData, error: keysError, @@ -35,8 +27,6 @@ export function OperatorProfilePage() { refetch: refetchStats, } = useGetOperatorStats(); - const isOperatorActive = user.status === 'active'; - useEffect(() => { if (keysData?.url) { void refetchStats(); @@ -58,168 +48,10 @@ export function OperatorProfilePage() { 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 ? ( - - ) : ( - - )} -
-
- - - - -
-
-
+
- - - {t('operator.profile.statistics.header')} - - - - - - - - - - - - - - - +
); diff --git a/packages/apps/human-app/frontend/src/modules/playground/components/modal-example/modal-example.tsx b/packages/apps/human-app/frontend/src/modules/playground/components/modal-example/modal-example.tsx deleted file mode 100644 index d9b083df9a..0000000000 --- a/packages/apps/human-app/frontend/src/modules/playground/components/modal-example/modal-example.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Stack } from '@mui/material'; -import Typography from '@mui/material/Typography'; - -export function ModalExample() { - return ( - -
- Example Modal - - Lorem ipsum dolor sit amet consectetur, adipisicing elit. Laborum - adipisci minima libero voluptates molestiae eligendi fugiat quas, - animi labore, perspiciatis quasi deleniti natus numquam laudantium - debitis, officia nostrum ad dolore! - -
-
- ); -} diff --git a/packages/apps/human-app/frontend/src/modules/playground/components/table-example/table-example.tsx b/packages/apps/human-app/frontend/src/modules/playground/components/table-example/table-example.tsx deleted file mode 100644 index 595d3aa878..0000000000 --- a/packages/apps/human-app/frontend/src/modules/playground/components/table-example/table-example.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { TableQueryContextProvider } from '@/shared/components/ui/table/table-query-context'; -import { Table } from '@/modules/playground/components/table-example/table'; - -export function TableExample() { - return ( - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/playground/components/table-example/table-search-form.tsx b/packages/apps/human-app/frontend/src/modules/playground/components/table-example/table-search-form.tsx deleted file mode 100644 index 454fc88446..0000000000 --- a/packages/apps/human-app/frontend/src/modules/playground/components/table-example/table-search-form.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import Search from '@mui/icons-material/Search'; -import InputAdornment from '@mui/material/InputAdornment'; -import { FormProvider, useForm } from 'react-hook-form'; -import { Input } from '@/shared/components/data-entry/input'; -import { useColorMode } from '@/shared/contexts/color-mode'; - -interface SearchFormProps { - label: string; - name: string; - placeholder: string; - columnId: string; - fullWidth?: boolean; - updater?: (fieldValue: string) => void; -} - -export function SearchForm({ - label, - name, - placeholder, - updater, - fullWidth = false, -}: SearchFormProps) { - const { colorPalette } = useColorMode(); - const methods = useForm<{ searchValue: string }>({ - defaultValues: { - searchValue: '', - }, - }); - - return ( - - - - - ), - }} - label={label} - name={name} - onChange={(e) => { - if (updater) { - updater(e.target.value); - } - }} - placeholder={placeholder} - sx={{ - width: fullWidth ? '100%' : '15rem', - margin: fullWidth ? '0' : '1rem', - }} - /> - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/playground/components/table-example/table-service.tsx b/packages/apps/human-app/frontend/src/modules/playground/components/table-example/table-service.tsx deleted file mode 100644 index b5204d5ea6..0000000000 --- a/packages/apps/human-app/frontend/src/modules/playground/components/table-example/table-service.tsx +++ /dev/null @@ -1,61 +0,0 @@ -export interface Person { - name: { - firstName: string; - lastName: string; - }; - address: string; - city: string; - state: string; -} - -const data: Person[] = [ - { - name: { - firstName: 'John', - lastName: 'Doe', - }, - address: '261 Erdman Ford', - city: 'East Daphne', - state: 'Kentucky', - }, - { - name: { - firstName: 'Jane', - lastName: 'Doe', - }, - address: '769 Dominic Grove', - city: 'Columbus', - state: 'Ohio', - }, - { - name: { - firstName: 'Joe', - lastName: 'Doe', - }, - address: '566 Brakus Inlet', - city: 'South Linda', - state: 'West Virginia', - }, - { - name: { - firstName: 'Kevin', - lastName: 'Vandy', - }, - address: '722 Emie Stream', - city: 'Lincoln', - state: 'Nebraska', - }, - { - name: { - firstName: 'Joshua', - lastName: 'Rolluffs', - }, - address: '32188 Larkin Turnpike', - city: 'Omaha', - state: 'Nebraska', - }, -]; - -export function getTableData(): Promise { - return Promise.resolve(data); -} diff --git a/packages/apps/human-app/frontend/src/modules/playground/components/table-example/table.tsx b/packages/apps/human-app/frontend/src/modules/playground/components/table-example/table.tsx deleted file mode 100644 index f086f2ac28..0000000000 --- a/packages/apps/human-app/frontend/src/modules/playground/components/table-example/table.tsx +++ /dev/null @@ -1,107 +0,0 @@ -import { - MaterialReactTable, - useMaterialReactTable, - type MRT_ColumnDef, -} from 'material-react-table'; -import { useQuery } from '@tanstack/react-query'; -import { TableHeaderCell } from '@/shared/components/ui/table/table-header-cell'; -import { useTableQuery } from '@/shared/components/ui/table/table-query-hook'; -import type { Person } from '@/modules/playground/components/table-example/table-service'; -import { getTableData } from '@/modules/playground/components/table-example/table-service'; -import { SearchForm } from '@/modules/playground/components/table-example/table-search-form'; -import { Sorting } from '@/shared/components/ui/table/table-header-menu.tsx/sorting'; -import { Filtering } from '@/shared/components/ui/table/table-header-menu.tsx/filtering'; - -const columns: MRT_ColumnDef[] = [ - { - accessorKey: 'name.firstName', - header: 'First Name', - size: 150, - enableSorting: true, - }, - { - accessorKey: 'name.lastName', - header: 'Last Name', - size: 150, - muiTableHeadCellProps: () => ({ - component: (props) => ( - undefined} - sortingOptions={[ - { label: 'test1', sortCallback: () => undefined }, - { label: 'test2', sortCallback: () => undefined }, - ]} - /> - } - /> - ), - }), - }, - { - accessorKey: 'address', - header: 'Address', - size: 200, - muiTableHeadCellProps: () => ({ - component: (props) => ( - undefined} - filteringOptions={[{ name: 'test', option: 'test' }]} - isChecked={(option) => option === 'test'} - setFiltering={() => undefined} - /> - } - /> - ), - }), - }, - { - accessorKey: 'city', - header: 'City', - size: 150, - }, - { - accessorKey: 'state', - header: 'State', - size: 150, - }, -]; - -export function Table() { - const { - fields: { sorting, pagination }, - } = useTableQuery(); - - const { data, isLoading, isError, isRefetching } = useQuery({ - queryKey: ['example', [sorting, pagination]], - queryFn: () => getTableData(), - }); - - const table = useMaterialReactTable({ - columns, - data: !data ? [] : data, - state: { - isLoading, - showAlertBanner: isError, - showProgressBars: isRefetching, - }, - enableColumnActions: false, - enableColumnFilters: false, - enableSorting: false, - renderTopToolbar: () => ( - - ), - }); - - return ; -} diff --git a/packages/apps/human-app/frontend/src/modules/playground/views/playground.page.tsx b/packages/apps/human-app/frontend/src/modules/playground/views/playground.page.tsx deleted file mode 100644 index b4e76710b5..0000000000 --- a/packages/apps/human-app/frontend/src/modules/playground/views/playground.page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import Paper from '@mui/material/Paper'; -import { FormExample } from '@/shared/components/data-entry/form-example'; -import { UiExample } from '@/shared/components/ui/ui-example'; - -export function Playground() { - return ( - - -

Form

- -
- ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/sign-in/schema.ts b/packages/apps/human-app/frontend/src/modules/signin/worker/schemas.ts similarity index 64% rename from packages/apps/human-app/frontend/src/modules/worker/services/sign-in/schema.ts rename to packages/apps/human-app/frontend/src/modules/signin/worker/schemas.ts index 2002a3a5e0..65903954a7 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/services/sign-in/schema.ts +++ b/packages/apps/human-app/frontend/src/modules/signin/worker/schemas.ts @@ -11,9 +11,4 @@ export const signInDtoSchema = z.object({ h_captcha_token: z.string().min(1, t('validation.captcha')).default('token'), }); -export const signInSuccessResponseSchema = z.object({ - // eslint-disable-next-line camelcase -- data from api - access_token: z.string(), - // eslint-disable-next-line camelcase -- data from api - refresh_token: z.string(), -}); +export type SignInDto = z.infer; 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 ca6e34ecf8..f4e11c0811 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 @@ -8,10 +8,9 @@ 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 { type SignInDto } from '@/modules/worker/services/sign-in/types'; -import { signInDtoSchema } from '@/modules/worker/services/sign-in/schema'; import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; import { HCaptchaForm } from '@/shared/components/hcaptcha'; +import { type SignInDto, signInDtoSchema } from './schemas'; interface SignInFormProps { onSubmit: (data: SignInDto) => void; diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/sign-in/sign-in.ts b/packages/apps/human-app/frontend/src/modules/signin/worker/use-sign-in-mutation.ts similarity index 85% rename from packages/apps/human-app/frontend/src/modules/worker/services/sign-in/sign-in.ts rename to packages/apps/human-app/frontend/src/modules/signin/worker/use-sign-in-mutation.ts index 2cfd14d324..2b54f8e727 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/services/sign-in/sign-in.ts +++ b/packages/apps/human-app/frontend/src/modules/signin/worker/use-sign-in-mutation.ts @@ -4,12 +4,12 @@ import { apiClient } from '@/api/api-client'; import { apiPaths } from '@/api/api-paths'; import { routerPaths } from '@/router/router-paths'; import { useAuth } from '@/modules/auth/hooks/use-auth'; -import { type SignInDto } from './types'; -import { signInSuccessResponseSchema } from './schema'; +import { authTokensSuccessResponseSchema } from '@/shared/schemas'; +import { type SignInDto } from './schemas'; function signInMutationFn(data: SignInDto) { return apiClient(apiPaths.worker.signIn.path, { - successSchema: signInSuccessResponseSchema, + successSchema: authTokensSuccessResponseSchema, options: { method: 'POST', body: JSON.stringify(data), 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 29121d5791..9368785558 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 @@ -1,7 +1,7 @@ import { useEffect } from 'react'; -import { useSignInMutation } from '@/modules/worker/services/sign-in/sign-in'; import { browserAuthProvider } from '@/shared/contexts/browser-auth-provider'; -import { type SignInDto } from '@/modules/worker/services/sign-in/types'; +import { useSignInMutation } from './use-sign-in-mutation'; +import { type SignInDto } from './schemas'; export function useSignIn() { const { diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/sign-up.ts b/packages/apps/human-app/frontend/src/modules/signup/worker/hooks/use-sign-up-mutation.ts similarity index 54% rename from packages/apps/human-app/frontend/src/modules/worker/services/sign-up.ts rename to packages/apps/human-app/frontend/src/modules/signup/worker/hooks/use-sign-up-mutation.ts index 3448c1b729..325b951b1e 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/services/sign-up.ts +++ b/packages/apps/human-app/frontend/src/modules/signup/worker/hooks/use-sign-up-mutation.ts @@ -1,39 +1,10 @@ import { z } from 'zod'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; -import { t } from 'i18next'; import { apiClient } from '@/api/api-client'; import { apiPaths } from '@/api/api-paths'; import { routerPaths } from '@/router/router-paths'; - -export const signUpDtoSchema = z - .object({ - email: z.string().email(t('validation.invalidEmail')), - // eslint-disable-next-line camelcase -- export vite config - h_captcha_token: z - .string() - .min(1, t('validation.captcha')) - .default('token'), - }) - .and( - z - .object({ - password: z - .string() - .min(8, t('validation.min')) - .max(50, t('validation.max', { count: 50 })), - confirmPassword: z - .string() - .min(1, t('validation.required')) - .max(50, t('validation.max', { count: 50 })), - }) - .refine(({ password, confirmPassword }) => confirmPassword === password, { - message: t('validation.passwordMismatch'), - path: ['confirmPassword'], - }) - ); - -export type SignUpDto = z.infer; +import { type SignUpDto } from '../schema'; const signUpSuccessResponseSchema = z.unknown(); 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 6dc975e461..bef557d9d1 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,8 +1,8 @@ import { useEffect } from 'react'; import omit from 'lodash/omit'; import { browserAuthProvider } from '@/shared/contexts/browser-auth-provider'; -import type { SignUpDto } from '@/modules/worker/services/sign-up'; -import { useSignUpMutation } from '@/modules/worker/services/sign-up'; +import { type SignUpDto } from '../schema'; +import { useSignUpMutation } from './use-sign-up-mutation'; export function useSignUpWorker() { const { diff --git a/packages/apps/human-app/frontend/src/modules/signup/worker/schema.ts b/packages/apps/human-app/frontend/src/modules/signup/worker/schema.ts new file mode 100644 index 0000000000..1aa96aa028 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/signup/worker/schema.ts @@ -0,0 +1,28 @@ +import { t } from 'i18next'; +import { z } from 'zod'; + +export const signUpDtoSchema = z + .object({ + email: z.string().email(t('validation.invalidEmail')), + // eslint-disable-next-line camelcase -- export vite config + h_captcha_token: z + .string() + .min(1, t('validation.captcha')) + .default('token'), + }) + .and( + z + .object({ + password: z + .string() + .min(8, t('validation.min')) + .max(50, t('validation.max', { count: 50 })), + confirmPassword: z.string().min(1, t('validation.required')), + }) + .refine(({ password, confirmPassword }) => confirmPassword === password, { + message: t('validation.passwordMismatch'), + path: ['confirmPassword'], + }) + ); + +export type SignUpDto = z.infer; 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 a52a2409e9..06f9696137 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 @@ -4,8 +4,6 @@ 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 type { SignUpDto } from '@/modules/worker/services/sign-up'; -import { signUpDtoSchema } from '@/modules/worker/services/sign-up'; import { Button } from '@/shared/components/ui/button'; import { Input } from '@/shared/components/data-entry/input'; import { Password } from '@/shared/components/data-entry/password/password'; @@ -17,6 +15,7 @@ import { HCaptchaForm } from '@/shared/components/hcaptcha'; import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; import { FetchError } from '@/api/fetcher'; import { useSignUpWorker } from '@/modules/signup/worker/hooks/use-sign-up-worker'; +import { signUpDtoSchema, type SignUpDto } from '../schema'; export function SignUpWorkerPage() { const { t } = useTranslation(); diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/available-jobs-job-type-filter.tsx b/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/available-jobs-job-type-filter.tsx deleted file mode 100644 index 166cf584ff..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/available-jobs-job-type-filter.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* eslint-disable camelcase --- ... */ -import { useTranslation } from 'react-i18next'; -import { Filtering } from '@/shared/components/ui/table/table-header-menu.tsx/filtering'; -import { useJobsFilterStore } from '@/modules/worker/hooks/use-jobs-filter-store'; -import { JOB_TYPES } from '@/shared/consts'; - -export function AvailableJobsJobTypeFilter() { - const { t } = useTranslation(); - const { setFilterParams, filterParams } = useJobsFilterStore(); - - return ( - { - setFilterParams({ - ...filterParams, - job_type: undefined, - }); - }} - filteringOptions={JOB_TYPES.map((jobType) => ({ - name: t(`jobTypeLabels.${jobType}`), - option: jobType, - }))} - isChecked={(option) => option === filterParams.job_type} - setFiltering={(jobType) => { - setFilterParams({ - ...filterParams, - job_type: jobType, - }); - }} - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/available-jobs-table.tsx b/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/available-jobs-table.tsx deleted file mode 100644 index c18401185b..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/available-jobs-table.tsx +++ /dev/null @@ -1,268 +0,0 @@ -/* eslint-disable camelcase -- ... */ -import type { MRT_ColumnDef } from 'material-react-table'; -import { - MaterialReactTable, - useMaterialReactTable, -} from 'material-react-table'; -import { t } from 'i18next'; -import { useEffect, useMemo, useState } from 'react'; -import { Grid } from '@mui/material'; -import { useJobsFilterStore } from '@/modules/worker/hooks/use-jobs-filter-store'; -import { - useGetAvailableJobsData, - type AvailableJob, -} from '@/modules/worker/services/available-jobs-data'; -import { useAssignJobMutation } from '@/modules/worker/services/use-assign-job'; -import { EvmAddress } from '@/modules/worker/components/jobs/evm-address'; -import { RewardAmount } from '@/modules/worker/components/jobs/reward-amount'; -import { getNetworkName } from '@/modules/smart-contracts/get-network-name'; -import { Chip } from '@/shared/components/ui/chip'; -import { useJobsNotifications } from '@/modules/worker/hooks/use-jobs-notifications'; -import { TableButton } from '@/shared/components/ui/table-button'; -import { TableHeaderCell } from '@/shared/components/ui/table/table-header-cell'; -import { AvailableJobsNetworkFilter } from '@/modules/worker/components/jobs/available-jobs/desktop/available-jobs-network-filter'; -import { AvailableJobsRewardAmountSort } from '@/modules/worker/components/jobs/available-jobs/desktop/available-jobs-reward-amount-sort'; -import { AvailableJobsJobTypeFilter } from '@/modules/worker/components/jobs/available-jobs/desktop/available-jobs-job-type-filter'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { createTableDarkMode } from '@/shared/styles/create-table-dark-mode'; -import type { JobType } from '@/modules/smart-contracts/EthKVStore/config'; -import { EscrowAddressSearchForm } from '@/modules/worker/components/jobs/escrow-address-search-form'; - -interface AvailableJobsTableProps { - chainIdsEnabled: number[]; -} - -export type AvailableJobsTableData = AvailableJob & { - rewardTokenInfo: { - reward_amount?: string; - reward_token?: string; - }; -}; - -const getColumns = ( - chainIdsEnabled: number[] -): MRT_ColumnDef[] => [ - { - accessorKey: 'job_description', - header: t('worker.jobs.jobDescription'), - size: 100, - enableSorting: false, - }, - { - accessorKey: 'escrow_address', - header: t('worker.jobs.escrowAddress'), - size: 100, - enableSorting: false, - Cell: (props) => { - return ; - }, - }, - { - accessorKey: 'chain_id', - header: t('worker.jobs.network'), - size: 100, - enableSorting: false, - Cell: (props) => { - return getNetworkName(props.row.original.chain_id); - }, - muiTableHeadCellProps: () => ({ - component: (props) => { - return ( - - } - /> - ); - }, - }), - }, - { - accessorKey: 'reward_amount', - header: t('worker.jobs.rewardAmount'), - size: 100, - enableSorting: false, - Cell: (props) => { - const { reward_amount, reward_token } = props.row.original; - return ( - - ); - }, - muiTableHeadCellProps: () => ({ - component: (props) => ( - } - /> - ), - }), - }, - { - accessorKey: 'job_type', - header: t('worker.jobs.jobType'), - size: 200, - enableSorting: false, - Cell: ({ row }) => { - const label = t(`jobTypeLabels.${row.original.job_type as JobType}`); - return ; - }, - muiTableHeadCellProps: () => ({ - component: (props) => { - return ( - } - /> - ); - }, - }), - }, - { - accessorKey: 'escrow_address', - id: 'selectJobAction', - header: '', - size: 100, - enableSorting: false, - Cell: (props) => { - const { escrow_address, chain_id } = props.row.original; - const { onJobAssignmentError, onJobAssignmentSuccess } = - useJobsNotifications(); - const { mutate: assignJobMutation, isPending } = useAssignJobMutation( - { - onSuccess: onJobAssignmentSuccess, - onError: onJobAssignmentError, - }, - [`assignJob-${escrow_address}`] - ); - - return ( - - { - assignJobMutation({ escrow_address, chain_id }); - }} - sx={{ - width: '94px', - }} - > - {t('worker.jobs.selectJob')} - - - ); - }, - }, -]; - -export function AvailableJobsTable({ - chainIdsEnabled, -}: Readonly) { - const { colorPalette, isDarkMode } = useColorMode(); - const { - setSearchEscrowAddress, - setPageParams, - filterParams, - resetFilterParams, - } = useJobsFilterStore(); - const { data: tableData, status: tableStatus } = useGetAvailableJobsData(); - const memoizedTableDataResults = useMemo( - () => tableData?.results ?? [], - [tableData?.results] - ); - - const [paginationState, setPaginationState] = useState({ - pageIndex: 0, - pageSize: 5, - }); - - useEffect(() => { - if (!(paginationState.pageSize === 5 || paginationState.pageSize === 10)) - return; - setPageParams(paginationState.pageIndex, paginationState.pageSize); - }, [paginationState, setPageParams]); - - useEffect(() => { - setPaginationState({ - pageIndex: filterParams.page, - pageSize: filterParams.page_size, - }); - }, [filterParams.page, filterParams.page_size]); - - useEffect(() => { - return () => { - resetFilterParams(); - }; - }, [resetFilterParams]); - - const columns: MRT_ColumnDef[] = getColumns(chainIdsEnabled); - - const table = useMaterialReactTable({ - columns, - data: memoizedTableDataResults, - state: { - isLoading: tableStatus === 'pending', - showAlertBanner: tableStatus === 'error', - pagination: paginationState, - }, - enablePagination: Boolean(tableData?.total_pages), - manualPagination: true, - onPaginationChange: setPaginationState, - muiPaginationProps: { - SelectProps: { - sx: { - '.MuiSelect-icon': { - ':hover': { - backgroundColor: 'blue', - }, - fill: colorPalette.text.primary, - }, - }, - }, - rowsPerPageOptions: [5, 10], - }, - pageCount: tableData?.total_pages ?? -1, - rowCount: tableData?.total_results, - enableColumnActions: false, - enableColumnFilters: false, - enableSorting: true, - manualSorting: true, - renderTopToolbar: () => ( - { - setSearchEscrowAddress(address); - }} - /> - ), - 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/components/jobs/available-jobs/mobile/available-jobs-job-type-filter-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-job-type-filter-mobile.tsx deleted file mode 100644 index d4ad256708..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-job-type-filter-mobile.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* eslint-disable camelcase --- ... */ -import { useTranslation } from 'react-i18next'; -import { Filtering } from '@/shared/components/ui/table/table-header-menu.tsx/filtering'; -import { useJobsFilterStore } from '@/modules/worker/hooks/use-jobs-filter-store'; -import { JOB_TYPES } from '@/shared/consts'; - -export function AvailableJobsJobTypeFilterMobile() { - const { t } = useTranslation(); - const { setFilterParams, filterParams } = useJobsFilterStore(); - - return ( - { - setFilterParams({ - ...filterParams, - job_type: undefined, - page: 0, - }); - }} - filteringOptions={JOB_TYPES.map((jobType) => ({ - name: t(`jobTypeLabels.${jobType}`), - option: jobType, - }))} - isChecked={(option) => option === filterParams.job_type} - isMobile={false} - setFiltering={(jobType) => { - setFilterParams({ - ...filterParams, - job_type: jobType, - page: 0, - }); - }} - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-network-filter-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-network-filter-mobile.tsx deleted file mode 100644 index a521fa3f75..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-network-filter-mobile.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* eslint-disable camelcase --- ... */ -import { useGetAllNetworks } from '@/modules/worker/hooks/use-get-all-networks'; -import { Filtering } from '@/shared/components/ui/table/table-header-menu.tsx/filtering'; -import { useJobsFilterStore } from '@/modules/worker/hooks/use-jobs-filter-store'; - -interface AvailableJobsNetworkFilterProps { - chainIdsEnabled: number[]; -} - -export function AvailableJobsNetworkFilterMobile({ - chainIdsEnabled, -}: AvailableJobsNetworkFilterProps) { - const { setFilterParams, filterParams } = useJobsFilterStore(); - const { allNetworks } = useGetAllNetworks(chainIdsEnabled); - - return ( - { - setFilterParams({ - ...filterParams, - chain_id: undefined, - page: 0, - }); - }} - filteringOptions={allNetworks} - isChecked={(option) => option === filterParams.chain_id} - isMobile={false} - setFiltering={(chainId) => { - setFilterParams({ - ...filterParams, - chain_id: chainId, - page: 0, - }); - }} - /> - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-table-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-table-mobile.tsx deleted file mode 100644 index fc503eb452..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-table-mobile.tsx +++ /dev/null @@ -1,178 +0,0 @@ -/* eslint-disable camelcase -- ... */ -import { Grid, List, Paper, Stack, Typography } from '@mui/material'; -import { useTranslation } from 'react-i18next'; -import { useEffect, useState, type Dispatch, type SetStateAction } from 'react'; -import { Button } from '@/shared/components/ui/button'; -import { FiltersButtonIcon } from '@/shared/components/ui/icons'; -import { useJobsFilterStore } from '@/modules/worker/hooks/use-jobs-filter-store'; -import { Alert } from '@/shared/components/ui/alert'; -import { getNetworkName } from '@/modules/smart-contracts/get-network-name'; -import { getErrorMessageForError } from '@/shared/errors'; -import { Loader } from '@/shared/components/ui/loader'; -import { EvmAddress } from '@/modules/worker/components/jobs/evm-address'; -import { Chip } from '@/shared/components/ui/chip'; -import { RewardAmount } from '@/modules/worker/components/jobs/reward-amount'; -import { ListItem } from '@/shared/components/ui/list-item'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import type { JobType } from '@/modules/smart-contracts/EthKVStore/config'; -import { EscrowAddressSearchForm } from '@/modules/worker/components/jobs/escrow-address-search-form'; -import { AvailableJobsAssignJobButton } from '@/modules/worker/components/jobs/available-jobs/mobile/available-jobs-assign-job-button'; -import { - type AvailableJob, - useInfiniteAvailableJobsQuery, -} from '@/modules/worker/services/available-jobs-data'; - -interface AvailableJobsTableMobileProps { - setIsMobileFilterDrawerOpen: Dispatch>; -} - -export function AvailableJobsTableMobile({ - setIsMobileFilterDrawerOpen, -}: Readonly) { - const { colorPalette } = useColorMode(); - const [allPages, setAllPages] = useState([]); - - const { - data: tableData, - status: tableStatus, - isError: isTableError, - error: tableError, - fetchNextPage, - hasNextPage, - } = useInfiniteAvailableJobsQuery(); - const { filterParams, setPageParams, resetFilterParams } = - useJobsFilterStore(); - const { t } = useTranslation(); - const { setSearchEscrowAddress } = useJobsFilterStore(); - - useEffect(() => { - if (!tableData) return; - const pagesFromRes = tableData.pages.flatMap((pages) => pages.results); - if (filterParams.page === 0) { - setAllPages(pagesFromRes); - } else { - setAllPages((state) => [...state, ...pagesFromRes]); - } - }, [tableData, filterParams.page]); - - useEffect(() => { - return () => { - resetFilterParams(); - }; - }, [resetFilterParams]); - - return ( - <> - - - - {isTableError ? ( - - {getErrorMessageForError(tableError)} - - ) : null} - {tableStatus === 'pending' ? ( - - - - ) : null} - {allPages.map((d) => ( - - - - - - - {d.job_description} - - - - - - - - - - - - - - - {getNetworkName(d.chain_id)} - - - - - - - - - - - - - - - ))} - {hasNextPage ? ( - - ) : null} - - - ); -} 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 index 4c21687f02..ef740aff1a 100644 --- 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 @@ -4,10 +4,10 @@ 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 type { ResendEmailVerificationDto } from '@/modules/worker/services/resend-email-verification'; import { HCaptchaForm } from '@/shared/components/hcaptcha/h-captcha-form'; import { MailTo } from '@/shared/components/ui/mail-to'; import { env } from '@/shared/env'; +import { type ResendEmailVerificationDto } from '../schemas'; interface ResendVerificationEmailFormProps { methods: UseFormReturn>; diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/resend-email-verification.ts b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/resend-email-verification.ts similarity index 52% rename from packages/apps/human-app/frontend/src/modules/worker/services/resend-email-verification.ts rename to packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/resend-email-verification.ts index 8bccfa7b41..ffd11f0306 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/services/resend-email-verification.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/resend-email-verification.ts @@ -1,35 +1,10 @@ -/* eslint-disable camelcase -- ...*/ import { z } from 'zod'; -import type { MutationState } from '@tanstack/react-query'; -import { - useMutation, - useMutationState, - useQueryClient, -} from '@tanstack/react-query'; -import last from 'lodash/last'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; import { t } from 'i18next'; import { apiClient } from '@/api/api-client'; import { apiPaths } from '@/api/api-paths'; -import type { ResponseError } from '@/shared/types/global.type'; import { useAuth } from '@/modules/auth/hooks/use-auth'; - -export const resendEmailVerificationHcaptchaSchema = z.object({ - h_captcha_token: z.string().min(1, t('validation.captcha')).default('token'), -}); - -export type ResendEmailVerificationHcaptchaDto = z.infer< - typeof resendEmailVerificationHcaptchaSchema ->; -export const resendEmailVerificationEmailSchema = z.object({ - email: z.string().email(), -}); - -export type ResendEmailVerificationEmailDto = z.infer< - typeof resendEmailVerificationEmailSchema ->; - -export type ResendEmailVerificationDto = ResendEmailVerificationHcaptchaDto & - ResendEmailVerificationEmailDto; +import { type ResendEmailVerificationDto } from '../schemas'; const ResendEmailVerificationSuccessResponseSchema = z.unknown(); @@ -67,14 +42,3 @@ export function useResendEmailVerificationWorkerMutation() { mutationKey: [resendEmailVerificationKey], }); } - -export function useResendEmailVerificationWorkerMutationState() { - const state = useMutationState({ - filters: { mutationKey: [resendEmailVerificationKey] }, - select: (mutation) => mutation.state, - }); - - return last(state) as - | MutationState - | undefined; -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email.ts b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email.ts index 9edae9d145..14df70a6ff 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/hooks/use-resend-email.ts @@ -1,12 +1,12 @@ /* eslint-disable camelcase -- ...*/ import { useForm } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; +import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; import { - useResendEmailVerificationWorkerMutation, + type ResendEmailVerificationDto, resendEmailVerificationHcaptchaSchema, -} from '@/modules/worker/services/resend-email-verification'; -import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; -import type { ResendEmailVerificationDto } from '@/modules/worker/services/resend-email-verification'; +} from '../schemas'; +import { useResendEmailVerificationWorkerMutation } from './resend-email-verification'; export function useResendEmail(email: string) { const { diff --git a/packages/apps/human-app/frontend/src/modules/worker/email-verification/schemas.ts b/packages/apps/human-app/frontend/src/modules/worker/email-verification/schemas.ts new file mode 100644 index 0000000000..931c2f46a8 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/email-verification/schemas.ts @@ -0,0 +1,22 @@ +import { t } from 'i18next'; +import { z } from 'zod'; + +export const resendEmailVerificationHcaptchaSchema = z.object({ + // eslint-disable-next-line camelcase + h_captcha_token: z.string().min(1, t('validation.captcha')).default('token'), +}); + +type ResendEmailVerificationHcaptchaDto = z.infer< + typeof resendEmailVerificationHcaptchaSchema +>; + +const resendEmailVerificationEmailSchema = z.object({ + email: z.string().email(), +}); + +type ResendEmailVerificationEmailDto = z.infer< + typeof resendEmailVerificationEmailSchema +>; + +export type ResendEmailVerificationDto = ResendEmailVerificationHcaptchaDto & + ResendEmailVerificationEmailDto; 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 e042ca0c2d..66fce91d97 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 @@ -10,7 +10,6 @@ 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 { useSolveHCaptchaMutation } from '@/modules/worker/services/solve-hcaptcha'; import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; import { useHCaptchaLabelingNotifications } from '@/modules/worker/hooks/use-hcaptcha-labeling-notifications'; import { useColorMode } from '@/shared/contexts/color-mode'; @@ -20,7 +19,11 @@ import { PageCardLoader, PageCardError, } from '@/shared/components/ui/page-card'; -import { useHCaptchaUserStats, useDailyHmtSpent } from './hooks'; +import { + useHCaptchaUserStats, + useDailyHmtSpent, + useSolveHCaptchaMutation, +} from './hooks'; export function HcaptchaLabelingPage() { const { colorPalette, isDarkMode } = useColorMode(); diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/index.ts index 2029fc304c..a825bac5de 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/index.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/index.ts @@ -1,3 +1,4 @@ export * from './use-daily-hmt-spent'; export * from './use-hcaptcha-user-stats'; export * from './enable-hcaptcha-labeling'; +export * from './use-solve-hcaptcha-mutation'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/solve-hcaptcha.ts b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/use-solve-hcaptcha-mutation.ts similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/services/solve-hcaptcha.ts rename to packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/use-solve-hcaptcha-mutation.ts diff --git a/packages/apps/human-app/frontend/src/modules/worker/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/worker/hooks/index.ts new file mode 100644 index 0000000000..b6aa06f63c --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/hooks/index.ts @@ -0,0 +1 @@ +export * from './use-get-oracles'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-get-oracles-notifications.tsx b/packages/apps/human-app/frontend/src/modules/worker/hooks/use-get-oracles-notifications.tsx index f29fe868e2..65d7280f15 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-get-oracles-notifications.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/hooks/use-get-oracles-notifications.tsx @@ -1,3 +1,4 @@ +import { useCallback } from 'react'; import { TopNotificationType, useNotification, @@ -8,13 +9,16 @@ import type { ResponseError } from '@/shared/types/global.type'; export function useGetOraclesNotifications() { const { showNotification } = useNotification(); - const onError = (error: ResponseError) => { - showNotification({ - type: TopNotificationType.WARNING, - message: getErrorMessageForError(error), - durationMs: 5000, - }); - }; + const onError = useCallback( + (error: ResponseError) => { + showNotification({ + type: TopNotificationType.WARNING, + message: getErrorMessageForError(error), + durationMs: 5000, + }); + }, + [showNotification] + ); return { onError }; } diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-get-oracles.ts b/packages/apps/human-app/frontend/src/modules/worker/hooks/use-get-oracles.ts similarity index 94% rename from packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-get-oracles.ts rename to packages/apps/human-app/frontend/src/modules/worker/hooks/use-get-oracles.ts index a4f888de54..f15e55945d 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-get-oracles.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/hooks/use-get-oracles.ts @@ -3,10 +3,10 @@ import { z } from 'zod'; import { useQuery } from '@tanstack/react-query'; import { apiClient } from '@/api/api-client'; import { apiPaths } from '@/api/api-paths'; -import { useJobsTypesOraclesFilter } from '@/modules/worker/hooks/use-job-types-oracles-table'; import { stringifyUrlQueryObject } from '@/shared/helpers/transfomers'; import { env } from '@/shared/env'; import { MainnetChains, TestnetChains } from '@/modules/smart-contracts/chains'; +import { useJobsTypesOraclesFilterStore } from '../jobs/hooks'; const OracleSchema = z.object({ address: z.string(), @@ -94,7 +94,7 @@ async function getOracles({ } export function useGetOracles() { - const { selected_job_types } = useJobsTypesOraclesFilter(); + const { selected_job_types } = useJobsTypesOraclesFilterStore(); return useQuery({ queryFn: ({ signal }) => getOracles({ selected_job_types, signal }), queryKey: ['oracles', selected_job_types], 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 index 3314b469ee..7111c46d80 100644 --- 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 @@ -5,7 +5,7 @@ import { 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 '../hooks'; +import { type Oracle } from '../../hooks'; interface OraclesTableDesktopProps { isOraclesDataPending: boolean; 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 index 44566c107d..e68f086405 100644 --- 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 @@ -2,12 +2,12 @@ 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 { EvmAddress } from '@/modules/worker/components/jobs/evm-address'; 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 { type Oracle } from '../hooks'; import { useSelectOracleNavigation } from '../hooks/use-select-oracle-navigation'; +import { EvmAddress } from '../../jobs/components'; +import { type Oracle } from '../../hooks'; interface OraclesTableItemMobileProps { oracle: Oracle; 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 index ef4499bf78..d41c0a9f36 100644 --- 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 @@ -2,14 +2,14 @@ import { t } from 'i18next'; import { FormProvider, useForm } from 'react-hook-form'; import { useEffect } from 'react'; import { Grid } from '@mui/material'; -import { useJobsTypesOraclesFilter } from '@/modules/worker/hooks/use-job-types-oracles-table'; 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 } = useJobsTypesOraclesFilter(); + const { selectJobType } = useJobsTypesOraclesFilterStore(); const methods = useForm<{ jobType: string[] }>({ defaultValues: { 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 index b4cd800329..2906bc2129 100644 --- 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 @@ -6,7 +6,7 @@ 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 { useGetOracles } from '../../hooks'; import { OraclesTableItemMobile } from './oracles-table-item-mobile'; import { OraclesTableDesktop } from './oracles-table-desktop'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.ts index 90af1f932c..4390786d84 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.ts @@ -1,4 +1,4 @@ -import { type Oracle } from '../hooks'; +import { type Oracle } from '../../hooks'; interface RegistrationResult { oracle_addresses: string[]; 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 index ba8a564805..e3515417de 100644 --- 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 @@ -1,2 +1 @@ -export * from './use-get-oracles'; 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 index 4dbafaec6d..ab9386d8bc 100644 --- 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 @@ -2,11 +2,11 @@ import { useMemo } from 'react'; import { t } from 'i18next'; import { Grid } from '@mui/material'; import type { MRT_ColumnDef } from 'material-react-table'; -import { EvmAddress } from '@/modules/worker/components/jobs/evm-address'; 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 { type Oracle } from './use-get-oracles'; +import { EvmAddress } from '../../jobs/components'; +import { type Oracle } from '../../hooks'; import { useSelectOracleNavigation } from './use-select-oracle-navigation'; export const useOraclesTableColumns = (): MRT_ColumnDef[] => { 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 index f3d90b872e..48a39887e8 100644 --- 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 @@ -3,8 +3,8 @@ 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 '../../hooks'; import { useGetRegistrationDataInOracles } from './use-get-registration-data-oracles'; -import { type Oracle } from './use-get-oracles'; const getHCaptchaPagePath = (siteKey: string | null | undefined): string => siteKey diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/index.ts index e171b27f6d..0011c13be6 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/index.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/index.ts @@ -1,2 +1,2 @@ export * from './jobs-discovery.page'; -export * from './hooks/use-get-oracles'; +export * from './hooks/use-get-registration-data-oracles'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-drawer-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/available-jobs-drawer-mobile-view.tsx similarity index 72% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-drawer-mobile.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/available-jobs-drawer-mobile-view.tsx index 8fc33beddc..29373aa307 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-drawer-mobile.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/available-jobs-drawer-mobile-view.tsx @@ -6,24 +6,30 @@ import { useTranslation } from 'react-i18next'; import CloseIcon from '@mui/icons-material/Close'; import type { Dispatch, SetStateAction } from 'react'; import { HumanLogoIcon } from '@/shared/components/ui/icons'; -import { AvailableJobsNetworkFilterMobile } from '@/modules/worker/components/jobs/available-jobs/mobile/available-jobs-network-filter-mobile'; import { useHandleMainNavIconClick } from '@/shared/hooks/use-handle-main-nav-icon-click'; -import { AvailableJobsJobTypeFilterMobile } from '@/modules/worker/components/jobs/available-jobs/mobile/available-jobs-job-type-filter-mobile'; -import { AvailableJobsRewardAmountSortMobile } from '@/modules/worker/components/jobs/available-jobs/mobile/available-jobs-reward-amount-sort-mobile'; import { useColorMode } from '@/shared/contexts/color-mode'; +import { + AvailableJobsNetworkFilter, + AvailableJobsJobTypeFilter, +} from './components'; +import { AvailableJobsRewardAmountSortMobile } from './components/mobile'; -interface DrawerMobileProps { +interface DrawerMobileViewProps { setIsMobileFilterDrawerOpen: Dispatch>; chainIdsEnabled: number[]; } -export function AvailableJobsDrawerMobile({ +export function AvailableJobsDrawerMobileView({ setIsMobileFilterDrawerOpen, chainIdsEnabled, -}: DrawerMobileProps) { +}: Readonly) { const handleMainNavIconClick = useHandleMainNavIconClick(); const { colorPalette } = useColorMode(); const { t } = useTranslation(); + const handleCloseDrawer = () => { + setIsMobileFilterDrawerOpen(false); + }; + return ( @@ -58,19 +64,12 @@ export function AvailableJobsDrawerMobile({ zIndex: '999999', }} > - { - handleMainNavIconClick(); - }} - > + { - setIsMobileFilterDrawerOpen(false); - }} + onClick={handleCloseDrawer} sx={{ zIndex: '99999999', marginRight: '15px', @@ -105,12 +104,8 @@ export function AvailableJobsDrawerMobile({ {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 new file mode 100644 index 0000000000..dd732ea662 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/available-jobs-view.tsx @@ -0,0 +1,23 @@ +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { AvailableJobsTableDesktop } from './components/desktop'; +import { AvailableJobsTableMobile } from './components/mobile'; + +interface AvailableJobsTableView { + handleOpenMobileFilterDrawer: () => void; + chainIdsEnabled: number[]; +} + +export function AvailableJobsView({ + handleOpenMobileFilterDrawer, + 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 new file mode 100644 index 0000000000..38455484ef --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/available-jobs-job-type-filter.tsx @@ -0,0 +1,38 @@ +/* eslint-disable camelcase --- ... */ +import { useTranslation } from 'react-i18next'; +import { useMemo } from 'react'; +import { Filtering } from '@/shared/components/ui/table/table-header-menu.tsx/filtering'; +import { JOB_TYPES } from '@/shared/consts'; +import { useJobsFilterStore } from '../../hooks'; + +export function AvailableJobsJobTypeFilter({ isMobile = 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} + isMobile={isMobile} + setFiltering={handleFilterChange} + /> + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/available-jobs-network-filter.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/available-jobs-network-filter.tsx similarity index 53% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/available-jobs-network-filter.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/available-jobs-network-filter.tsx index 15e040fcbe..47f5dce0ae 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/available-jobs-network-filter.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/available-jobs-network-filter.tsx @@ -1,34 +1,34 @@ /* eslint-disable camelcase --- ... */ import { Filtering } from '@/shared/components/ui/table/table-header-menu.tsx/filtering'; -import { useJobsFilterStore } from '@/modules/worker/hooks/use-jobs-filter-store'; -import { useGetAllNetworks } from '@/modules/worker/hooks/use-get-all-networks'; +import { useGetAllNetworks, useJobsFilterStore } from '../../hooks'; interface AvailableJobsNetworkFilterProps { chainIdsEnabled: number[]; + isMobile?: boolean; } export function AvailableJobsNetworkFilter({ chainIdsEnabled, -}: AvailableJobsNetworkFilterProps) { + isMobile = 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 ( { - setFilterParams({ - ...filterParams, - chain_id: undefined, - }); - }} + clear={handleClear} filteringOptions={allNetworks} isChecked={(option) => option === filterParams.chain_id} - setFiltering={(chainId) => { - setFilterParams({ - ...filterParams, - chain_id: chainId, - }); - }} + isMobile={isMobile} + setFiltering={handleFilterChange} /> ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/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 similarity index 70% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/available-jobs-reward-amount-sort.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/available-jobs-reward-amount-sort.tsx index 003ab5fa36..8f65c5f9fe 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/desktop/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 @@ -1,24 +1,23 @@ /* eslint-disable camelcase --- ... */ import { t } from 'i18next'; import { Sorting } from '@/shared/components/ui/table/table-header-menu.tsx/sorting'; -import { useJobsFilterStore } from '@/modules/worker/hooks/use-jobs-filter-store'; +import { useJobsFilterStore } from '../../hooks'; +import { SortDirection, SortField } from '../../types'; export function AvailableJobsRewardAmountSort() { - const { setFilterParams, filterParams } = useJobsFilterStore(); + const { setFilterParams } = useJobsFilterStore(); const sortAscRewardAmount = () => { setFilterParams({ - ...filterParams, - sort_field: 'reward_amount', - sort: 'asc', + sort_field: SortField.REWARD_AMOUNT, + sort: SortDirection.ASC, }); }; const sortDescRewardAmount = () => { setFilterParams({ - ...filterParams, - sort_field: 'reward_amount', - sort: 'desc', + sort_field: SortField.REWARD_AMOUNT, + sort: SortDirection.DESC, }); }; @@ -26,7 +25,6 @@ export function AvailableJobsRewardAmountSort() { { setFilterParams({ - ...filterParams, sort_field: undefined, sort: undefined, }); 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 new file mode 100644 index 0000000000..b20ecf2e28 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/desktop/available-jobs-table-desktop.tsx @@ -0,0 +1,106 @@ +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) { + const { colorPalette, isDarkMode } = useColorMode(); + const { data: tableData, status: tableStatus } = useGetAvailableJobsData(); + const { + setSearchEscrowAddress, + setPageParams, + filterParams, + resetFilterParams, + } = useJobsFilterStore(); + const { paginationState, setPaginationState } = useAvailableJobsPagination({ + setPageParams, + filterParams, + }); + const columns = useGetAvailableJobsColumns(chainIdsEnabled); + + const memoizedTableDataResults = useMemo( + () => tableData?.results ?? [], + [tableData?.results] + ); + + useEffect(() => { + return () => { + resetFilterParams(); + }; + }, [resetFilterParams]); + + const table = useMaterialReactTable({ + columns, + data: memoizedTableDataResults, + state: { + isLoading: tableStatus === 'pending', + showAlertBanner: tableStatus === 'error', + pagination: paginationState, + }, + enablePagination: Boolean(tableData?.total_pages), + manualPagination: true, + onPaginationChange: setPaginationState, + muiPaginationProps: { + SelectProps: { + sx: { + '.MuiSelect-icon': { + ':hover': { + backgroundColor: 'blue', + }, + fill: colorPalette.text.primary, + }, + }, + }, + rowsPerPageOptions: [5, 10], + }, + pageCount: tableData?.total_pages ?? -1, + rowCount: tableData?.total_results, + enableColumnActions: false, + enableColumnFilters: false, + enableSorting: true, + manualSorting: true, + renderTopToolbar: () => ( + { + setSearchEscrowAddress(address); + }} + /> + ), + 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/available-jobs/components/desktop/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/desktop/index.ts new file mode 100644 index 0000000000..cbd6a6feea --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/desktop/index.ts @@ -0,0 +1 @@ +export * from './available-jobs-table-desktop'; 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 new file mode 100644 index 0000000000..9d9f97fed7 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/index.ts @@ -0,0 +1,3 @@ +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/components/jobs/available-jobs/mobile/available-jobs-assign-job-button.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-assign-job-button-mobile.tsx similarity index 81% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-assign-job-button.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-assign-job-button-mobile.tsx index 260f3d0833..98cfd6cd51 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-assign-job-button.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-assign-job-button-mobile.tsx @@ -1,16 +1,16 @@ import { t } from 'i18next'; +import { TableButton } from '@/shared/components/ui/table-button'; +import { useJobsNotifications } from '../../../hooks'; import { type AssignJobBody, useAssignJobMutation, -} from '@/modules/worker/services/use-assign-job'; -import { TableButton } from '@/shared/components/ui/table-button'; -import { useJobsNotifications } from '@/modules/worker/hooks/use-jobs-notifications'; +} from '../../hooks/use-assign-job'; -export function AvailableJobsAssignJobButton({ +export function AvailableJobsAssignJobButtonMobile({ assignJobPayload, -}: { +}: Readonly<{ assignJobPayload: AssignJobBody; -}) { +}>) { const { onJobAssignmentError, onJobAssignmentSuccess } = useJobsNotifications(); 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 new file mode 100644 index 0000000000..73d12ac553 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-list-mobile.tsx @@ -0,0 +1,137 @@ +/* eslint-disable camelcase -- ... */ +import { Grid, List, Paper, Stack, Typography } from '@mui/material'; +import { useTranslation } from 'react-i18next'; +import { useEffect } from 'react'; +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, + type AvailableJob, +} from '../../hooks/use-get-available-jobs-data'; +import { EvmAddress, RewardAmount } from '../../../components'; +import { AvailableJobsAssignJobButtonMobile } from './available-jobs-assign-job-button-mobile'; + +export function AvailableJobsListMobile() { + const { t } = useTranslation(); + const { colorPalette } = useColorMode(); + const { filterParams, setPageParams, resetFilterParams } = + useJobsFilterStore(); + const { + data: tableData, + status: tableStatus, + isError: isTableError, + error: tableError, + fetchNextPage, + hasNextPage, + } = useInifiniteGetAvailableJobsData(); + + const allPages = useCombinePages(tableData, filterParams.page); + + useEffect(() => { + return () => { + resetFilterParams(); + }; + }, [resetFilterParams]); + + return ( + + {isTableError && ( + + {getErrorMessageForError(tableError)} + + )} + {tableStatus === 'pending' && ( + + + + )} + + {allPages.map((d) => ( + + + + + + + {d.job_description} + + + + + + + + + + + + + + + {getNetworkName(d.chain_id)} + + + + + + + + + + + + + + + ))} + {hasNextPage ? ( + + ) : null} + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/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 similarity index 61% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/mobile/available-jobs-reward-amount-sort-mobile.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-reward-amount-sort-mobile.tsx index ce6704f8e3..bd16dee084 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/available-jobs/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 @@ -2,8 +2,9 @@ import { t } from 'i18next'; import Typography from '@mui/material/Typography'; import { useColorMode } from '@/shared/contexts/color-mode'; -import { useJobsFilterStore } from '@/modules/worker/hooks/use-jobs-filter-store'; -import { Sorting } from '@/modules/worker/components/jobs/sorting'; +import { useJobsFilterStore } from '../../../hooks'; +import { Sorting } from '../../../components'; +import { SortDirection, SortField } from '../../../types'; export function AvailableJobsRewardAmountSortMobile() { const { setFilterParams, filterParams } = useJobsFilterStore(); @@ -17,30 +18,27 @@ export function AvailableJobsRewardAmountSortMobile() { } fromHighestSelected={ - filterParams.sort_field === 'reward_amount' && - filterParams.sort === 'desc' + filterParams.sort_field === SortField.REWARD_AMOUNT && + filterParams.sort === SortDirection.DESC } sortFromHighest={() => { setFilterParams({ - ...filterParams, - sort: 'desc', - sort_field: 'reward_amount', + sort: SortDirection.DESC, + sort_field: SortField.REWARD_AMOUNT, }); }} fromLowestSelected={ - filterParams.sort_field === 'reward_amount' && - filterParams.sort === 'asc' + filterParams.sort_field === SortField.REWARD_AMOUNT && + filterParams.sort === SortDirection.ASC } sortFromLowest={() => { setFilterParams({ - ...filterParams, - sort: 'asc', - sort_field: 'reward_amount', + sort: SortDirection.ASC, + sort_field: SortField.REWARD_AMOUNT, }); }} clear={() => { setFilterParams({ - ...filterParams, 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 new file mode 100644 index 0000000000..e056ed10ce --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/available-jobs-table-mobile.tsx @@ -0,0 +1,42 @@ +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 { AvailableJobsListMobile } from './available-jobs-list-mobile'; + +interface AvailableJobsTableMobileProps { + handleOpenMobileFilterDrawer: () => void; +} + +export function AvailableJobsTableMobile({ + handleOpenMobileFilterDrawer, +}: Readonly) { + const { t } = useTranslation(); + const { setSearchEscrowAddress } = useJobsFilterStore(); + + 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 new file mode 100644 index 0000000000..04265ea0ed --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/components/mobile/index.ts @@ -0,0 +1,2 @@ +export * from './available-jobs-reward-amount-sort-mobile'; +export * from './available-jobs-table-mobile'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/index.ts new file mode 100644 index 0000000000..b0cfd707d8 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/index.ts @@ -0,0 +1,3 @@ +export * from './use-get-available-jobs-columns'; +export * from './use-get-available-jobs-data'; +export * from './use-assign-job'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/use-assign-job.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-assign-job.ts similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/services/use-assign-job.ts rename to packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-assign-job.ts diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-available-jobs-pagination.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-available-jobs-pagination.tsx new file mode 100644 index 0000000000..c44dab7a5d --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-available-jobs-pagination.tsx @@ -0,0 +1,33 @@ +import { useState, useEffect } from 'react'; +import type { PageSize } from '@/shared/types/entity.type'; +import { type JobsFilterStoreProps } from '../../hooks'; + +interface PaginationProps { + setPageParams: (pageIndex: number, pageSize: PageSize) => void; + filterParams: JobsFilterStoreProps['filterParams']; +} + +export const useAvailableJobsPagination = ({ + setPageParams, + filterParams, +}: PaginationProps) => { + const [paginationState, setPaginationState] = useState({ + pageIndex: 0, + pageSize: 5, + }); + + useEffect(() => { + if (!(paginationState.pageSize === 5 || paginationState.pageSize === 10)) + return; + setPageParams(paginationState.pageIndex, paginationState.pageSize); + }, [paginationState, setPageParams]); + + useEffect(() => { + setPaginationState({ + pageIndex: filterParams.page, + pageSize: filterParams.page_size, + }); + }, [filterParams.page, filterParams.page_size]); + + return { paginationState, setPaginationState }; +}; 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 new file mode 100644 index 0000000000..3bba5a3acb --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-get-available-jobs-columns.tsx @@ -0,0 +1,155 @@ +/* eslint-disable camelcase -- ... */ +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 { 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 { useJobsNotifications } from '../../hooks'; +import { EvmAddress, RewardAmount } from '../../components'; +import { + AvailableJobsNetworkFilter, + AvailableJobsRewardAmountSort, + AvailableJobsJobTypeFilter, +} from '../components'; +import { type AvailableJob } from './use-get-available-jobs-data'; +import { useAssignJobMutation } from './use-assign-job'; + +const COL_SIZE = 100; +const COL_SIZE_LG = 200; + +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'), + size: COL_SIZE, + enableSorting: false, + Cell: (props) => { + return ; + }, + }, + { + accessorKey: 'chain_id', + header: t('worker.jobs.network'), + size: COL_SIZE, + enableSorting: false, + Cell: (props) => { + return getNetworkName(props.row.original.chain_id); + }, + muiTableHeadCellProps: () => ({ + component: (props) => { + return ( + + } + /> + ); + }, + }), + }, + { + accessorKey: 'reward_amount', + header: t('worker.jobs.rewardAmount'), + size: COL_SIZE, + enableSorting: false, + Cell: (props) => { + const { reward_amount, reward_token } = props.row.original; + return ( + + ); + }, + muiTableHeadCellProps: () => ({ + component: (props) => ( + } + /> + ), + }), + }, + { + accessorKey: 'job_type', + header: t('worker.jobs.jobType'), + size: COL_SIZE_LG, + enableSorting: false, + Cell: ({ row }) => { + const label = t(`jobTypeLabels.${row.original.job_type as JobType}`); + return ; + }, + muiTableHeadCellProps: () => ({ + component: (props) => { + return ( + } + /> + ); + }, + }), + }, + { + accessorKey: 'escrow_address', + id: 'selectJobAction', + header: '', + size: COL_SIZE, + enableSorting: false, + Cell: (props) => { + const { escrow_address, chain_id } = props.row.original; + const { onJobAssignmentError, onJobAssignmentSuccess } = + useJobsNotifications(); + const { mutate: assignJobMutation, isPending } = useAssignJobMutation( + { + onSuccess: onJobAssignmentSuccess, + onError: onJobAssignmentError, + }, + [`assignJob-${escrow_address}`] + ); + + return ( + + { + assignJobMutation({ escrow_address, chain_id }); + }} + sx={{ + width: '94px', + }} + > + {t('worker.jobs.selectJob')} + + + ); + }, + }, + ], + [chainIdsEnabled] + ); +}; diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/available-jobs-data.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-get-available-jobs-data.ts similarity index 91% rename from packages/apps/human-app/frontend/src/modules/worker/services/available-jobs-data.ts rename to packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-get-available-jobs-data.ts index f0dfff4f6b..212a919a29 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/services/available-jobs-data.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-get-available-jobs-data.ts @@ -5,9 +5,8 @@ import { useParams } from 'react-router-dom'; import { apiClient } from '@/api/api-client'; import { apiPaths } from '@/api/api-paths'; import { stringifyUrlQueryObject } from '@/shared/helpers/transfomers'; -import type { JobsFilterStoreProps } from '@/modules/worker/hooks/use-jobs-filter-store'; -import { useJobsFilterStore } from '@/modules/worker/hooks/use-jobs-filter-store'; import { createPaginationSchema } from '@/shared/helpers/pagination'; +import { type JobsFilterStoreProps, useJobsFilterStore } from '../../hooks'; const availableJobSchema = z.object({ escrow_address: z.string(), @@ -59,7 +58,7 @@ export function useGetAvailableJobsData() { }); } -export function useInfiniteAvailableJobsQuery() { +export function useInifiniteGetAvailableJobsData() { const { filterParams } = useJobsFilterStore(); const { address: oracleAddress } = useParams<{ address: string }>(); 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 new file mode 100644 index 0000000000..b4efea1d49 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/index.ts @@ -0,0 +1,2 @@ +export * from './available-jobs-view'; +export * from './available-jobs-drawer-mobile-view'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/escrow-address-search-form.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/escrow-address-search-form.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/escrow-address-search-form.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/components/escrow-address-search-form.tsx diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/evm-address.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/evm-address.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/evm-address.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/components/evm-address.tsx 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 new file mode 100644 index 0000000000..9dee5f52de --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/index.ts @@ -0,0 +1,7 @@ +export * from './evm-address'; +export * from './jobs-tab-panel'; +export * from './my-jobs-table-actions'; +export * from './escrow-address-search-form'; +export * from './reject-button'; +export * from './reward-amount'; +export * from './sorting'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/jobs-tab-panel.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/jobs-tab-panel.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/jobs-tab-panel.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/components/jobs-tab-panel.tsx diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs-table-actions.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/my-jobs-table-actions.tsx similarity index 78% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs-table-actions.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/components/my-jobs-table-actions.tsx index 1470a87cb8..3d2d5341ad 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs-table-actions.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/my-jobs-table-actions.tsx @@ -1,19 +1,19 @@ /* eslint-disable camelcase -- ...*/ import { Link, useParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; -import { useRejectTaskMutation } from '@/modules/worker/services/reject-task'; import { TableButton } from '@/shared/components/ui/table-button'; -import { RejectButton } from '@/modules/worker/components/jobs/reject-button'; -import { - MyJobStatus, - type MyJob, -} from '@/modules/worker/services/my-jobs-data'; +import { useRejectTaskMutation } from '../my-jobs/hooks'; +import { MyJobStatus } from '../types'; +import { type MyJob } from '../my-jobs/schemas'; +import { RejectButton } from './reject-button'; interface MyJobsTableRejectActionProps { job: MyJob; } -export function MyJobsTableActions({ job }: MyJobsTableRejectActionProps) { +export function MyJobsTableActions({ + job, +}: Readonly) { const { t } = useTranslation(); const { mutate: rejectTaskMutation, isPending: isRejectPending } = useRejectTaskMutation(); diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/reject-button.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/reject-button.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/reject-button.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/components/reject-button.tsx diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/reward-amount.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/reward-amount.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/reward-amount.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/components/reward-amount.tsx diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/sorting.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/sorting.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/sorting.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/components/sorting.tsx 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 new file mode 100644 index 0000000000..d0d0034f9d --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/index.ts @@ -0,0 +1,7 @@ +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-ui-config'; +export * from './use-get-my-jobs-data'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-get-all-networks.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-all-networks.ts similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/hooks/use-get-all-networks.ts rename to packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-all-networks.ts diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/my-jobs-data.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-my-jobs-data.ts similarity index 54% rename from packages/apps/human-app/frontend/src/modules/worker/services/my-jobs-data.ts rename to packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-my-jobs-data.ts index 3d72f81054..f293ebb507 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/services/my-jobs-data.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-my-jobs-data.ts @@ -1,52 +1,14 @@ -/* eslint-disable camelcase -- api response*/ +/* eslint-disable camelcase */ import { useInfiniteQuery, useQuery } from '@tanstack/react-query'; -import { z } from 'zod'; import { useParams } from 'react-router-dom'; import { apiClient } from '@/api/api-client'; import { apiPaths } from '@/api/api-paths'; import { stringifyUrlQueryObject } from '@/shared/helpers/transfomers'; -import { createPaginationSchema } from '@/shared/helpers/pagination'; -import type { MyJobsFilterStoreProps } from '@/modules/worker/hooks/use-my-jobs-filter-store'; -import { useMyJobsFilterStore } from '@/modules/worker/hooks/use-my-jobs-filter-store'; - -export enum MyJobStatus { - ACTIVE = 'ACTIVE', - CANCELED = 'CANCELED', - COMPLETED = 'COMPLETED', - VALIDATION = 'VALIDATION', - EXPIRED = 'EXPIRED', - REJECTED = 'REJECTED', -} - -export const UNKNOWN_JOB_STATUS = 'UNKNOWN'; - -const myJobSchema = z.object({ - assignment_id: z.string(), - escrow_address: z.string(), - chain_id: z.number(), - job_type: z.string(), - status: z.string().transform((value) => { - try { - return z.nativeEnum(MyJobStatus).parse(value.toUpperCase()); - } catch (error) { - return UNKNOWN_JOB_STATUS; - } - }), - reward_amount: z.string(), - reward_token: z.string(), - created_at: z.string(), - expires_at: z.string(), - url: z.string().optional().nullable(), -}); - -const myJobsSuccessResponseSchema = createPaginationSchema(myJobSchema); - -export type MyJob = z.infer; -export type MyJobsSuccessResponse = z.infer; -export interface MyJobsWithJobTypes { - jobTypes: string[]; - jobs: MyJobsSuccessResponse; -} +import { myJobsSuccessResponseSchema } from '../my-jobs/schemas'; +import { + useMyJobsFilterStore, + type MyJobsFilterStoreProps, +} from './use-my-jobs-filter-store'; type GetMyJobTableDataDto = MyJobsFilterStoreProps['filterParams'] & { oracle_address: string; diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/get-ui-config.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-ui-config.ts similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/services/get-ui-config.ts rename to packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-ui-config.ts diff --git a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-job-types-oracles-table.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-job-types-oracles-table.tsx similarity index 78% rename from packages/apps/human-app/frontend/src/modules/worker/hooks/use-job-types-oracles-table.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-job-types-oracles-table.tsx index 229a0472eb..60943b3ecf 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-job-types-oracles-table.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-job-types-oracles-table.tsx @@ -6,8 +6,8 @@ export interface JobsTypesOraclesFilterStore { selectJobType: (jobType: string[]) => void; } -export const useJobsTypesOraclesFilter = create( - (set) => ({ +export const useJobsTypesOraclesFilterStore = + create((set) => ({ selected_job_types: [], selectJobType: (jobTypes: string[]) => { set((state) => ({ @@ -15,5 +15,4 @@ export const useJobsTypesOraclesFilter = create( selected_job_types: jobTypes, })); }, - }) -); + })); diff --git a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-jobs-filter-store.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-filter-store.tsx similarity index 90% rename from packages/apps/human-app/frontend/src/modules/worker/hooks/use-jobs-filter-store.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-filter-store.tsx index 68ce87e540..fa739bd147 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-jobs-filter-store.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-filter-store.tsx @@ -1,17 +1,12 @@ /* eslint-disable camelcase -- api params*/ import { create } from 'zustand'; import type { PageSize } from '@/shared/types/entity.type'; -import { MyJobStatus } from '@/modules/worker/services/my-jobs-data'; +import { MyJobStatus, type SortDirection, type SortField } from '../types'; export interface JobsFilterStoreProps { filterParams: { - sort?: 'asc' | 'desc'; - sort_field?: - | 'chain_id' - | 'job_type' - | 'reward_amount' - | 'created_at' - | 'escrow_address'; + sort?: SortDirection; + sort_field?: SortField; network?: 'MATIC' | 'POLYGON'; // TODO add allowed job types job_type?: string; diff --git a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-jobs-notifications.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-notifications.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/hooks/use-jobs-notifications.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-notifications.tsx diff --git a/packages/apps/human-app/frontend/src/modules/worker/hooks/use-my-jobs-filter-store.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-my-jobs-filter-store.tsx similarity index 88% rename from packages/apps/human-app/frontend/src/modules/worker/hooks/use-my-jobs-filter-store.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-my-jobs-filter-store.tsx index 074439b923..4e2063ded5 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/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,17 +1,12 @@ /* eslint-disable camelcase -- api params*/ import { create } from 'zustand'; import type { PageSize } from '@/shared/types/entity.type'; -import { type MyJobStatus } from '@/modules/worker/services/my-jobs-data'; +import { SortDirection, SortField, type MyJobStatus } from '../types'; export interface MyJobsFilterStoreProps { filterParams: { - sort?: 'asc' | 'desc'; - sort_field?: - | 'chain_id' - | 'job_type' - | 'reward_amount' - | 'expires_at' - | 'created_at'; + sort?: SortDirection; + sort_field?: SortField; job_type?: string; status?: MyJobStatus; escrow_address?: string; @@ -34,8 +29,8 @@ const initialFiltersState = { escrow_address: '', page: 0, page_size: 5, - sort_field: 'created_at', - sort: 'desc', + sort_field: SortField.CREATED_AT, + sort: SortDirection.DESC, } as const; export const useMyJobsFilterStore = create((set) => ({ 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 new file mode 100644 index 0000000000..846093be12 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/index.ts @@ -0,0 +1 @@ +export * from './jobs.page'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/views/jobs/jobs.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/jobs.page.tsx similarity index 63% rename from packages/apps/human-app/frontend/src/modules/worker/views/jobs/jobs.page.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/jobs.page.tsx index 31aa77491f..cc211c2853 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/views/jobs/jobs.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/jobs.page.tsx @@ -1,24 +1,24 @@ /* eslint-disable camelcase */ -import React, { useEffect, useRef, useState } from 'react'; +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 { Modal } from '@/shared/components/ui/modal/modal'; import { useIsMobile } from '@/shared/hooks/use-is-mobile'; -import { MyJobsTableMobile } from '@/modules/worker/components/jobs/my-jobs/mobile/my-jobs-table-mobile'; -import { AvailableJobsTable } from '@/modules/worker/components/jobs/available-jobs/desktop/available-jobs-table'; -import { MyJobsDrawerMobile } from '@/modules/worker/components/jobs/my-jobs/mobile/my-jobs-drawer-mobile'; -import { AvailableJobsDrawerMobile } from '@/modules/worker/components/jobs/available-jobs/mobile/available-jobs-drawer-mobile'; -import { useGetOracles } from '@/modules/worker/jobs-discovery'; -import { useGetUiConfig } from '@/modules/worker/services/get-ui-config'; import { useColorMode } from '@/shared/contexts/color-mode'; -import { useGetOraclesNotifications } from '@/modules/worker/hooks/use-get-oracles-notifications'; import { NoRecords } from '@/shared/components/ui/no-records'; -import { AvailableJobsTableMobile } from '@/modules/worker/components/jobs/available-jobs/mobile/available-jobs-table-mobile'; -import { TabPanel } from '@/modules/worker/components/jobs/jobs-tab-panel'; -import { MyJobsTable } from '@/modules/worker/components/jobs/my-jobs/desktop/my-jobs-table'; import { PageCardLoader } from '@/shared/components/ui/page-card'; +import { useGetOracles } from '../hooks'; +import { useGetOraclesNotifications } from '../hooks/use-get-oracles-notifications'; +import { useGetUiConfig } from './hooks'; +import { TabPanel } from './components'; +import { MyJobsDrawerMobileView } from './my-jobs/components/mobile'; +import { + AvailableJobsView, + AvailableJobsDrawerMobileView, +} from './available-jobs'; +import { MyJobsView } from './my-jobs/my-jobs-view'; function generateTabA11yProps(index: number) { return { @@ -54,7 +54,6 @@ export function JobsPage() { const [isMobileFilterDrawerOpen, setIsMobileFilterDrawerOpen] = useState(false); const { onError } = useGetOraclesNotifications(); - const onErrorRef = useRef(onError); const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => { setActiveTab(newValue); @@ -66,11 +65,15 @@ export function JobsPage() { } }; + const handleOpenMobileFilterDrawer = () => { + setIsMobileFilterDrawerOpen(true); + }; + useEffect(() => { if (error) { - onErrorRef.current(error); + onError(error); } - }, [error]); + }, [error, onError]); const oracleName = data?.find( ({ address }) => address === oracle_address @@ -84,13 +87,13 @@ export function JobsPage() { <> {selectedTab === 'availableJobs' && uiConfigData && ( - )} {selectedTab === 'myJobs' && uiConfigData && ( - @@ -111,19 +114,17 @@ export function JobsPage() { backgroundColor: isMobile ? 'transparent' : undefined, }} > -
- {!isError && ( - - {oracleName} - - )} -
+ {!isError && ( + + {oracleName} + + )} @@ -152,38 +153,24 @@ export function JobsPage() { {isError ? ( ) : ( - <> - {isMobile ? ( - - ) : ( - - )} - + )} {isError ? ( ) : ( - <> - {isMobile ? ( - - ) : ( - - )} - + )} diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/desktop/my-jobs-table.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/columns.tsx similarity index 51% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/desktop/my-jobs-table.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/columns.tsx index 9b66f0c267..0ae8322952 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/desktop/my-jobs-table.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/columns.tsx @@ -1,44 +1,28 @@ /* eslint-disable camelcase -- ...*/ import { t } from 'i18next'; -import { useEffect, useMemo, useState } from 'react'; import Grid from '@mui/material/Grid'; -import { useParams } from 'react-router-dom'; -import { - MaterialReactTable, - useMaterialReactTable, - type MRT_ColumnDef, -} from 'material-react-table'; +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 { - useGetMyJobsData, - type MyJob, -} from '@/modules/worker/services/my-jobs-data'; -import { useMyJobsFilterStore } from '@/modules/worker/hooks/use-my-jobs-filter-store'; import { getNetworkName } from '@/modules/smart-contracts/get-network-name'; -import { RewardAmount } from '@/modules/worker/components/jobs/reward-amount'; import { Button } from '@/shared/components/ui/button'; import { Chip } from '@/shared/components/ui/chip'; -import { EvmAddress } from '@/modules/worker/components/jobs/evm-address'; -import { MyJobsJobTypeFilter } from '@/modules/worker/components/jobs/my-jobs/desktop/my-jobs-job-type-filter'; -import { MyJobsRewardAmountSort } from '@/modules/worker/components/jobs/my-jobs/desktop/my-jobs-reward-amount-sort'; -import { MyJobsStatusFilter } from '@/modules/worker/components/jobs/my-jobs/desktop/my-jobs-status-filter'; -import { MyJobsExpiresAtSort } from '@/modules/worker/components/jobs/my-jobs/desktop/my-jobs-expires-at-sort'; -import { MyJobsNetworkFilter } from '@/modules/worker/components/jobs/my-jobs/desktop/my-jobs-network-filter'; -import { useColorMode } from '@/shared/contexts/color-mode'; -import { createTableDarkMode } from '@/shared/styles/create-table-dark-mode'; import type { JobType } from '@/modules/smart-contracts/EthKVStore/config'; -import { EscrowAddressSearchForm } from '@/modules/worker/components/jobs/escrow-address-search-form'; -import { useRefreshTasksMutation } from '@/modules/worker/services/refresh-tasks'; -import { StatusChip } from '@/modules/worker/components/jobs/status-chip'; import { formatDate } from '@/shared/helpers/date'; -import { MyJobsTableActions } from '../../my-jobs-table-actions'; - -interface MyJobsTableProps { - chainIdsEnabled: number[]; -} +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'; -const getColumnsDefinition = ({ +export const getColumnsDefinition = ({ refreshData, isRefreshTasksPending, chainIdsEnabled, @@ -212,117 +196,3 @@ const getColumnsDefinition = ({ }), }, ]; - -export function MyJobsTable({ chainIdsEnabled }: MyJobsTableProps) { - const { colorPalette, isDarkMode } = useColorMode(); - const { - setSearchEscrowAddress, - setPageParams, - filterParams, - resetFilterParams, - } = useMyJobsFilterStore(); - const { data: tableData, status: tableStatus } = useGetMyJobsData(); - - const memoizedTableDataResults = useMemo( - () => tableData?.results ?? [], - [tableData?.results] - ); - - const { mutate: refreshTasksMutation, isPending: isRefreshTasksPending } = - useRefreshTasksMutation(); - const { address: oracle_address } = useParams<{ address: string }>(); - - const [paginationState, setPaginationState] = useState({ - pageIndex: 0, - pageSize: 5, - }); - - const refreshTasks = (address: string) => { - return () => { - refreshTasksMutation({ oracle_address: address }); - }; - }; - useEffect(() => { - if (!(paginationState.pageSize === 5 || paginationState.pageSize === 10)) - return; - setPageParams(paginationState.pageIndex, paginationState.pageSize); - }, [paginationState, setPageParams]); - - useEffect(() => { - setPaginationState({ - pageIndex: filterParams.page, - pageSize: filterParams.page_size, - }); - }, [filterParams.page, filterParams.page_size]); - - useEffect(() => { - return () => { - resetFilterParams(); - }; - }, [resetFilterParams]); - - const table = useMaterialReactTable({ - columns: getColumnsDefinition({ - refreshData: refreshTasks(oracle_address ?? ''), - isRefreshTasksPending, - chainIdsEnabled, - }), - data: memoizedTableDataResults, - state: { - isLoading: tableStatus === 'pending', - showAlertBanner: tableStatus === 'error', - pagination: paginationState, - }, - enablePagination: Boolean(tableData?.total_pages), - manualPagination: true, - onPaginationChange: (updater) => { - setPaginationState(updater); - }, - muiPaginationProps: { - SelectProps: { - sx: { - '.MuiSelect-icon': { - ':hover': { - backgroundColor: 'blue', - }, - fill: colorPalette.text.primary, - }, - }, - }, - rowsPerPageOptions: [5, 10], - }, - pageCount: tableData?.total_pages ?? -1, - rowCount: tableData?.total_results, - enableColumnActions: false, - enableColumnFilters: false, - enableSorting: false, - renderTopToolbar: () => ( - { - setSearchEscrowAddress(address); - }} - /> - ), - 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/my-jobs/components/desktop/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/index.ts new file mode 100644 index 0000000000..677305df30 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/index.ts @@ -0,0 +1 @@ +export * from './my-jobs-table'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 similarity index 70% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/desktop/my-jobs-expires-at-sort.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-expires-at-sort.tsx index 7ec9cb2c83..dc48b16472 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 @@ -1,24 +1,23 @@ /* eslint-disable camelcase --- ... */ import { t } from 'i18next'; -import { useMyJobsFilterStore } from '@/modules/worker/hooks/use-my-jobs-filter-store'; import { Sorting } from '@/shared/components/ui/table/table-header-menu.tsx/sorting'; +import { useMyJobsFilterStore } from '../../../hooks'; +import { SortDirection, SortField } from '../../../types'; export function MyJobsExpiresAtSort() { - const { setFilterParams, filterParams } = useMyJobsFilterStore(); + const { setFilterParams } = useMyJobsFilterStore(); const sortAscExpiresAt = () => { setFilterParams({ - ...filterParams, - sort_field: 'expires_at', - sort: 'asc', + sort_field: SortField.EXPIRES_AT, + sort: SortDirection.ASC, }); }; const sortDescExpiresAt = () => { setFilterParams({ - ...filterParams, - sort_field: 'expires_at', - sort: 'desc', + sort_field: SortField.EXPIRES_AT, + sort: SortDirection.DESC, }); }; @@ -26,7 +25,6 @@ export function MyJobsExpiresAtSort() { { setFilterParams({ - ...filterParams, sort_field: undefined, sort: undefined, }); diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 similarity index 76% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/desktop/my-jobs-job-type-filter.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-job-type-filter.tsx index fd5b5293c1..1992596ac0 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 @@ -1,29 +1,33 @@ /* eslint-disable camelcase --- ... */ import { useTranslation } from 'react-i18next'; -import { useMyJobsFilterStore } from '@/modules/worker/hooks/use-my-jobs-filter-store'; +import { useMemo } from 'react'; import { Filtering } from '@/shared/components/ui/table/table-header-menu.tsx/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({ - ...filterParams, job_type: undefined, }); }} - filteringOptions={JOB_TYPES.map((jobType) => ({ - name: t(`jobTypeLabels.${jobType}`), - option: jobType, - }))} + filteringOptions={filteringOptions} isChecked={(option) => option === filterParams.job_type} setFiltering={(jobType) => { setFilterParams({ - ...filterParams, job_type: jobType, }); }} diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 similarity index 74% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/desktop/my-jobs-network-filter.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-network-filter.tsx index 82b4690514..71e099b10f 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 @@ -1,7 +1,6 @@ /* eslint-disable camelcase --- ... */ -import { useMyJobsFilterStore } from '@/modules/worker/hooks/use-my-jobs-filter-store'; -import { useGetAllNetworks } from '@/modules/worker/hooks/use-get-all-networks'; import { Filtering } from '@/shared/components/ui/table/table-header-menu.tsx/filtering'; +import { useMyJobsFilterStore, useGetAllNetworks } from '../../../hooks'; interface MyJobsNetworkFilterProps { chainIdsEnabled: number[]; @@ -9,7 +8,7 @@ interface MyJobsNetworkFilterProps { export function MyJobsNetworkFilter({ chainIdsEnabled, -}: MyJobsNetworkFilterProps) { +}: Readonly) { const { setFilterParams, filterParams } = useMyJobsFilterStore(); const { allNetworks } = useGetAllNetworks(chainIdsEnabled); @@ -17,7 +16,6 @@ export function MyJobsNetworkFilter({ { setFilterParams({ - ...filterParams, chain_id: undefined, }); }} @@ -25,7 +23,6 @@ export function MyJobsNetworkFilter({ isChecked={(option) => option === filterParams.chain_id} setFiltering={(chainId) => { setFilterParams({ - ...filterParams, chain_id: chainId, }); }} diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 similarity index 70% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/desktop/my-jobs-reward-amount-sort.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-reward-amount-sort.tsx index 397f697ffe..293d32abae 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 @@ -1,24 +1,23 @@ /* eslint-disable camelcase --- ... */ import { t } from 'i18next'; -import { useMyJobsFilterStore } from '@/modules/worker/hooks/use-my-jobs-filter-store'; import { Sorting } from '@/shared/components/ui/table/table-header-menu.tsx/sorting'; +import { useMyJobsFilterStore } from '../../../hooks'; +import { SortDirection, SortField } from '../../../types'; export function MyJobsRewardAmountSort() { - const { setFilterParams, filterParams } = useMyJobsFilterStore(); + const { setFilterParams } = useMyJobsFilterStore(); const sortAscRewardAmount = () => { setFilterParams({ - ...filterParams, - sort_field: 'reward_amount', - sort: 'asc', + sort_field: SortField.REWARD_AMOUNT, + sort: SortDirection.ASC, }); }; const sortDescRewardAmount = () => { setFilterParams({ - ...filterParams, - sort_field: 'reward_amount', - sort: 'desc', + sort_field: SortField.REWARD_AMOUNT, + sort: SortDirection.DESC, }); }; @@ -26,7 +25,6 @@ export function MyJobsRewardAmountSort() { { setFilterParams({ - ...filterParams, sort_field: undefined, sort: undefined, }); diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 similarity index 76% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/desktop/my-jobs-status-filter.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-status-filter.tsx index 363e0a8299..285f1e334e 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 @@ -1,7 +1,7 @@ import capitalize from 'lodash/capitalize'; -import { useMyJobsFilterStore } from '@/modules/worker/hooks/use-my-jobs-filter-store'; import { Filtering } from '@/shared/components/ui/table/table-header-menu.tsx/filtering'; -import { MyJobStatus } from '@/modules/worker/services/my-jobs-data'; +import { useMyJobsFilterStore } from '../../../hooks'; +import { MyJobStatus } from '../../../types'; export function MyJobsStatusFilter() { const { setFilterParams, filterParams } = useMyJobsFilterStore(); @@ -10,7 +10,6 @@ export function MyJobsStatusFilter() { { setFilterParams({ - ...filterParams, status: undefined, }); }} @@ -21,7 +20,6 @@ export function MyJobsStatusFilter() { isChecked={(status) => status === filterParams.status} setFiltering={(status) => { setFilterParams({ - ...filterParams, status, }); }} 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 new file mode 100644 index 0000000000..cee3a0ec9f --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-table.tsx @@ -0,0 +1,131 @@ +/* eslint-disable camelcase -- ...*/ +import { t } from 'i18next'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { + MaterialReactTable, + 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 { useRefreshTasksMutation } from '../../hooks'; +import { getColumnsDefinition } from './columns'; + +interface MyJobsTableProps { + chainIdsEnabled: number[]; +} + +export function MyJobsTable({ chainIdsEnabled }: Readonly) { + const { colorPalette, isDarkMode } = useColorMode(); + const { + setSearchEscrowAddress, + setPageParams, + filterParams, + resetFilterParams, + } = useMyJobsFilterStore(); + const { data: tableData, status: tableStatus } = useGetMyJobsData(); + + const memoizedTableDataResults = useMemo( + () => tableData?.results ?? [], + [tableData?.results] + ); + + const { mutate: refreshTasksMutation, isPending: isRefreshTasksPending } = + useRefreshTasksMutation(); + 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]); + + useEffect(() => { + if (paginationState.pageSize === 5 || paginationState.pageSize === 10) { + setPageParams(paginationState.pageIndex, paginationState.pageSize); + } + }, [paginationState, setPageParams]); + + useEffect(() => { + setPaginationState({ + pageIndex: filterParams.page, + pageSize: filterParams.page_size, + }); + }, [filterParams.page, filterParams.page_size]); + + useEffect(() => { + return () => { + resetFilterParams(); + }; + }, [resetFilterParams]); + + const table = useMaterialReactTable({ + columns: getColumnsDefinition({ + refreshData, + isRefreshTasksPending, + chainIdsEnabled, + }), + data: memoizedTableDataResults, + state: { + isLoading: tableStatus === 'pending', + showAlertBanner: tableStatus === 'error', + pagination: paginationState, + }, + enablePagination: Boolean(tableData?.total_pages), + manualPagination: true, + onPaginationChange: (updater) => { + setPaginationState(updater); + }, + muiPaginationProps: { + SelectProps: { + sx: { + '.MuiSelect-icon': { + ':hover': { + backgroundColor: 'blue', + }, + fill: colorPalette.text.primary, + }, + }, + }, + rowsPerPageOptions: [5, 10], + }, + pageCount: tableData?.total_pages ?? -1, + rowCount: tableData?.total_results, + enableColumnActions: false, + enableColumnFilters: false, + enableSorting: false, + renderTopToolbar: () => ( + { + setSearchEscrowAddress(address); + }} + /> + ), + 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/components/jobs/status-chip.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/status-chip.tsx similarity index 76% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/status-chip.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/status-chip.tsx index 5d33c3ecc0..dc64a3abfa 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/status-chip.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/status-chip.tsx @@ -1,11 +1,11 @@ import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; -import { type MyJob } from '@/modules/worker/services/my-jobs-data'; import { colorPalette as lightModeColorPalette } from '@/shared/styles/color-palette'; -import { getChipStatusColor } from '@/modules/worker/utils/get-chip-status-color'; import { useColorMode } from '@/shared/contexts/color-mode'; +import { getChipStatusColor } from '../../utils'; +import { type MyJob } from '../../schemas'; -export function StatusChip({ status }: { status: MyJob['status'] }) { +export function StatusChip({ status }: Readonly<{ status: MyJob['status'] }>) { const { colorPalette } = useColorMode(); 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 new file mode 100644 index 0000000000..ec2a70a7f5 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/index.ts @@ -0,0 +1,2 @@ +export * from './my-jobs-drawer-mobile'; +export * from './my-jobs-list-mobile'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/mobile/my-jobs-drawer-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-drawer-mobile.tsx similarity index 87% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/mobile/my-jobs-drawer-mobile.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-drawer-mobile.tsx index 9355052137..35a5fd25e3 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/mobile/my-jobs-drawer-mobile.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-drawer-mobile.tsx @@ -7,21 +7,21 @@ import CloseIcon from '@mui/icons-material/Close'; import type { Dispatch, SetStateAction } from 'react'; import { HumanLogoIcon } from '@/shared/components/ui/icons'; import { useColorMode } from '@/shared/contexts/color-mode'; -import { MyJobsRewardAmountSortMobile } from '@/modules/worker/components/jobs/my-jobs/mobile/my-jobs-reward-amount-sort-mobile'; -import { MyJobsExpiresAtSortMobile } from '@/modules/worker/components/jobs/my-jobs/mobile/my-jobs-expires-at-sort-mobile'; 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 DrawerMobileProps { setIsMobileFilterDrawerOpen: Dispatch>; chainIdsEnabled: number[]; } -export function MyJobsDrawerMobile({ +export function MyJobsDrawerMobileView({ setIsMobileFilterDrawerOpen, chainIdsEnabled, -}: DrawerMobileProps) { +}: Readonly) { const handleMainNavIconClick = useHandleMainNavIconClick(); const { colorPalette } = useColorMode(); const { t } = useTranslation(); @@ -108,11 +108,7 @@ export function MyJobsDrawerMobile({ {t('worker.jobs.network')} - + @@ -125,11 +121,7 @@ export function MyJobsDrawerMobile({ {t('worker.jobs.jobType')} - + } fromHighestSelected={ - filterParams.sort_field === 'expires_at' && filterParams.sort === 'desc' + filterParams.sort_field === SortField.EXPIRES_AT && + filterParams.sort === SortDirection.DESC } sortFromHighest={() => { setFilterParams({ - ...filterParams, - sort: 'desc', - sort_field: 'expires_at', + sort: SortDirection.DESC, + sort_field: SortField.EXPIRES_AT, }); }} fromLowestSelected={ - filterParams.sort_field === 'expires_at' && filterParams.sort === 'asc' + filterParams.sort_field === SortField.EXPIRES_AT && + filterParams.sort === SortDirection.ASC } sortFromLowest={() => { setFilterParams({ - ...filterParams, - sort: 'asc', - sort_field: 'expires_at', + sort: SortDirection.ASC, + sort_field: SortField.EXPIRES_AT, }); }} clear={() => { setFilterParams({ - ...filterParams, sort: undefined, sort_field: undefined, }); diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 similarity index 86% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/mobile/my-jobs-job-type-filter-mobile.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-job-type-filter-mobile.tsx index 3ed59dc791..6c570f07f5 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 @@ -1,8 +1,8 @@ /* eslint-disable camelcase --- ... */ import { useTranslation } from 'react-i18next'; -import { useMyJobsFilterStore } from '@/modules/worker/hooks/use-my-jobs-filter-store'; import { Filtering } from '@/shared/components/ui/table/table-header-menu.tsx/filtering'; import { JOB_TYPES } from '@/shared/consts'; +import { useMyJobsFilterStore } from '../../../hooks'; export function MyJobsJobTypeFilterMobile() { const { t } = useTranslation(); @@ -12,7 +12,6 @@ export function MyJobsJobTypeFilterMobile() { { setFilterParams({ - ...filterParams, job_type: undefined, page: 0, }); @@ -25,7 +24,6 @@ export function MyJobsJobTypeFilterMobile() { isMobile={false} setFiltering={(jobType) => { setFilterParams({ - ...filterParams, job_type: jobType, page: 0, }); diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/mobile/my-jobs-table-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-list-mobile.tsx similarity index 83% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/mobile/my-jobs-table-mobile.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-list-mobile.tsx index 02cb08a5e9..e930cda990 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/mobile/my-jobs-table-mobile.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-list-mobile.tsx @@ -1,40 +1,44 @@ /* eslint-disable camelcase -- ... */ import { Grid, List, Paper, Stack, Typography } from '@mui/material'; import { useTranslation } from 'react-i18next'; -import { useEffect, useState, type Dispatch, type SetStateAction } from 'react'; +import { useEffect, type Dispatch, type SetStateAction } 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 { useJobsFilterStore } from '@/modules/worker/hooks/use-jobs-filter-store'; -import type { MyJob } from '@/modules/worker/services/my-jobs-data'; -import { useInfiniteGetMyJobsData } from '@/modules/worker/services/my-jobs-data'; import { getErrorMessageForError } from '@/shared/errors'; -import { useMyJobsFilterStore } from '@/modules/worker/hooks/use-my-jobs-filter-store'; import { ListItem } from '@/shared/components/ui/list-item'; -import { EvmAddress } from '@/modules/worker/components/jobs/evm-address'; -import { RewardAmount } from '@/modules/worker/components/jobs/reward-amount'; import { useColorMode } from '@/shared/contexts/color-mode'; import { Chip } from '@/shared/components/ui/chip'; import type { JobType } from '@/modules/smart-contracts/EthKVStore/config'; -import { EscrowAddressSearchForm } from '@/modules/worker/components/jobs/escrow-address-search-form'; import { colorPalette as lightModeColorPalette } from '@/shared/styles/color-palette'; -import { useRefreshTasksMutation } from '@/modules/worker/services/refresh-tasks'; -import { getChipStatusColor } from '@/modules/worker/utils/get-chip-status-color'; import { formatDate } from '@/shared/helpers/date'; -import { MyJobsTableActions } from '../../my-jobs-table-actions'; +import { useCombinePages } from '@/shared/hooks'; +import { + EscrowAddressSearchForm, + EvmAddress, + RewardAmount, + MyJobsTableActions, +} from '../../../components'; +import { + useMyJobsFilterStore, + useJobsFilterStore, + useInfiniteGetMyJobsData, +} from '../../../hooks'; +import { useRefreshTasksMutation } from '../../hooks'; +import { getChipStatusColor } from '../../utils'; +import { type MyJob } from '../../schemas'; -interface MyJobsTableMobileProps { +interface MyJobsListMobileProps { setIsMobileFilterDrawerOpen: Dispatch>; } -export function MyJobsTableMobile({ +export function MyJobsListMobile({ setIsMobileFilterDrawerOpen, -}: MyJobsTableMobileProps) { +}: Readonly) { const { colorPalette } = useColorMode(); - const [allPages, setAllPages] = useState([]); const { filterParams, setPageParams, resetFilterParams } = useMyJobsFilterStore(); @@ -53,15 +57,7 @@ export function MyJobsTableMobile({ const { setSearchEscrowAddress } = useJobsFilterStore(); const { address: oracle_address } = useParams<{ address: string }>(); - useEffect(() => { - if (!tableData) return; - const pagesFromRes = tableData.pages.flatMap((pages) => pages.results); - if (filterParams.page === 0) { - setAllPages(pagesFromRes); - } else { - setAllPages((state) => [...state, ...pagesFromRes]); - } - }, [tableData, filterParams.page]); + const allPages = useCombinePages(tableData, filterParams.page); useEffect(() => { return () => { diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 similarity index 75% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/mobile/my-jobs-network-filter-mobile.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-network-filter-mobile.tsx index c1bb8ac908..9bfbc7e5ba 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 @@ -1,7 +1,6 @@ /* eslint-disable camelcase --- ... */ -import { useMyJobsFilterStore } from '@/modules/worker/hooks/use-my-jobs-filter-store'; import { Filtering } from '@/shared/components/ui/table/table-header-menu.tsx/filtering'; -import { useGetAllNetworks } from '@/modules/worker/hooks/use-get-all-networks'; +import { useMyJobsFilterStore, useGetAllNetworks } from '../../../hooks'; interface MyJobsNetworkFilterMobileProps { chainIdsEnabled: number[]; @@ -9,7 +8,7 @@ interface MyJobsNetworkFilterMobileProps { export function MyJobsNetworkFilterMobile({ chainIdsEnabled, -}: MyJobsNetworkFilterMobileProps) { +}: Readonly) { const { setFilterParams, filterParams } = useMyJobsFilterStore(); const { allNetworks } = useGetAllNetworks(chainIdsEnabled); @@ -17,7 +16,6 @@ export function MyJobsNetworkFilterMobile({ { setFilterParams({ - ...filterParams, chain_id: undefined, page: 0, }); @@ -27,7 +25,6 @@ export function MyJobsNetworkFilterMobile({ isMobile={false} setFiltering={(chainId) => { setFilterParams({ - ...filterParams, chain_id: chainId, page: 0, }); diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 similarity index 60% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/mobile/my-jobs-reward-amount-sort-mobile.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-reward-amount-sort-mobile.tsx index 818276a65a..b40381ae3c 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 @@ -2,12 +2,15 @@ import Typography from '@mui/material/Typography'; import { t } from 'i18next'; import { useColorMode } from '@/shared/contexts/color-mode'; -import { useMyJobsFilterStore } from '@/modules/worker/hooks/use-my-jobs-filter-store'; -import { Sorting } from '@/modules/worker/components/jobs/sorting'; +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 ( } fromHighestSelected={ - filterParams.sort_field === 'reward_amount' && - filterParams.sort === 'desc' + isRewardAmountSortSelected && filterParams.sort === SortDirection.DESC } sortFromHighest={() => { setFilterParams({ - ...filterParams, - sort: 'desc', - sort_field: 'reward_amount', + sort: SortDirection.DESC, + sort_field: SortField.REWARD_AMOUNT, }); }} fromLowestSelected={ - filterParams.sort_field === 'reward_amount' && - filterParams.sort === 'asc' + isRewardAmountSortSelected && filterParams.sort === SortDirection.ASC } sortFromLowest={() => { setFilterParams({ - ...filterParams, - sort: 'asc', - sort_field: 'reward_amount', + sort: SortDirection.ASC, + sort_field: SortField.REWARD_AMOUNT, }); }} clear={() => { setFilterParams({ - ...filterParams, sort: undefined, sort_field: undefined, }); diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 similarity index 77% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/mobile/my-jobs-status-filter-mobile.tsx rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-status-filter-mobile.tsx index 3f82a4b3a3..ce4f90c46a 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/my-jobs/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 @@ -1,7 +1,7 @@ import capitalize from 'lodash/capitalize'; -import { useMyJobsFilterStore } from '@/modules/worker/hooks/use-my-jobs-filter-store'; import { Filtering } from '@/shared/components/ui/table/table-header-menu.tsx/filtering'; -import { MyJobStatus } from '@/modules/worker/services/my-jobs-data'; +import { useMyJobsFilterStore } from '../../../hooks'; +import { MyJobStatus } from '../../../types'; export function MyJobsStatusFilterMobile() { const { setFilterParams, filterParams } = useMyJobsFilterStore(); @@ -10,7 +10,6 @@ export function MyJobsStatusFilterMobile() { { setFilterParams({ - ...filterParams, status: undefined, page: 0, }); @@ -22,7 +21,6 @@ export function MyJobsStatusFilterMobile() { isChecked={(status) => status === filterParams.status} setFiltering={(status) => { setFilterParams({ - ...filterParams, status, page: 0, }); diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/index.ts new file mode 100644 index 0000000000..d4e2db7bde --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/index.ts @@ -0,0 +1,2 @@ +export * from './refresh-tasks'; +export * from './reject-task'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/refresh-tasks.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/refresh-tasks.ts similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/services/refresh-tasks.ts rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/refresh-tasks.ts diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/reject-task.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/reject-task.ts similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/services/reject-task.ts rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/reject-task.ts 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 new file mode 100644 index 0000000000..099413099b --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/my-jobs-view.tsx @@ -0,0 +1,22 @@ +import { type Dispatch, type SetStateAction } from 'react'; +import { useIsMobile } from '@/shared/hooks'; +import { MyJobsTable } from './components/desktop'; +import { MyJobsListMobile } from './components/mobile'; + +export function MyJobsView({ + setIsMobileFilterDrawerOpen, + chainIdsEnabled, +}: { + setIsMobileFilterDrawerOpen: Dispatch>; + chainIdsEnabled: number[]; +}) { + const isMobile = useIsMobile(); + + return isMobile ? ( + + ) : ( + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/schemas.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/schemas.ts new file mode 100644 index 0000000000..7801675733 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/schemas.ts @@ -0,0 +1,27 @@ +/* eslint-disable camelcase -- api response*/ +import { z } from 'zod'; +import { createPaginationSchema } from '@/shared/helpers/pagination'; +import { MyJobStatus, UNKNOWN_JOB_STATUS } from '../types'; + +const myJobSchema = z.object({ + assignment_id: z.string(), + escrow_address: z.string(), + chain_id: z.number(), + job_type: z.string(), + status: z.string().transform((value) => { + try { + return z.nativeEnum(MyJobStatus).parse(value.toUpperCase()); + } catch (error) { + return UNKNOWN_JOB_STATUS; + } + }), + reward_amount: z.string(), + reward_token: z.string(), + created_at: z.string(), + expires_at: z.string(), + url: z.string().optional().nullable(), +}); + +export const myJobsSuccessResponseSchema = createPaginationSchema(myJobSchema); + +export type MyJob = z.infer; diff --git a/packages/apps/human-app/frontend/src/modules/worker/utils/get-chip-status-color.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/get-chip-status-color.ts similarity index 82% rename from packages/apps/human-app/frontend/src/modules/worker/utils/get-chip-status-color.ts rename to packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/get-chip-status-color.ts index 435e51ce1e..c791586ee8 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/utils/get-chip-status-color.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/get-chip-status-color.ts @@ -1,8 +1,5 @@ -import { - MyJobStatus, - type UNKNOWN_JOB_STATUS, -} from '@/modules/worker/services/my-jobs-data'; 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, 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 new file mode 100644 index 0000000000..bfddc6caac --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/index.ts @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000000..79e9900eb0 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/types.ts @@ -0,0 +1,24 @@ +export enum MyJobStatus { + ACTIVE = 'ACTIVE', + CANCELED = 'CANCELED', + COMPLETED = 'COMPLETED', + VALIDATION = 'VALIDATION', + EXPIRED = 'EXPIRED', + REJECTED = 'REJECTED', +} + +export const UNKNOWN_JOB_STATUS = 'UNKNOWN'; + +export enum SortField { + CHAIN_ID = 'chain_id', + JOB_TYPE = 'job_type', + REWARD_AMOUNT = 'reward_amount', + CREATED_AT = 'created_at', + ESCROW_ADDRESS = 'escrow_address', + EXPIRES_AT = 'expires_at', +} + +export enum SortDirection { + ASC = 'asc', + DESC = 'desc', +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/sevices/registration-in-exchange-oracles.ts b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-exchange-oracle-registration-mutation.ts similarity index 65% rename from packages/apps/human-app/frontend/src/modules/worker/oracle-registration/sevices/registration-in-exchange-oracles.ts rename to packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-exchange-oracle-registration-mutation.ts index 7406a2336c..12536bbe94 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/sevices/registration-in-exchange-oracles.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-exchange-oracle-registration-mutation.ts @@ -1,24 +1,8 @@ -/* eslint-disable camelcase */ import { z } from 'zod'; import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { ethers } from 'ethers'; -import { t } from 'i18next'; import { apiClient } from '@/api/api-client'; import { apiPaths } from '@/api/api-paths'; - -export const registrationInExchangeOracleDtoSchema = z.object({ - oracle_address: z - .string() - .refine( - (address) => ethers.isAddress(address), - t('validation.invalidOracleAddress') - ), - h_captcha_token: z.string().min(1, t('validation.captcha')).default('token'), -}); - -export type RegistrationInExchangeOracleDto = z.infer< - typeof registrationInExchangeOracleDtoSchema ->; +import { type RegistrationInExchangeOracleDto } from '../schema'; const RegistrationInExchangeOracleSuccessResponseSchema = z.unknown(); diff --git a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-is-already-registered.tsx b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-is-already-registered.ts similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-is-already-registered.tsx rename to packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-is-already-registered.ts diff --git a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-oracle-instructions.tsx b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-oracle-instructions.ts similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-oracle-instructions.tsx rename to packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-oracle-instructions.ts diff --git a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-oracle-registration-flow.tsx b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-oracle-registration-flow.ts similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-oracle-registration-flow.tsx rename to packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-oracle-registration-flow.ts diff --git a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-oracle-registration.tsx b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-oracle-registration.ts similarity index 84% rename from packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-oracle-registration.tsx rename to packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-oracle-registration.ts index 828fa67d69..40b716d31a 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-oracle-registration.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/hooks/use-oracle-registration.ts @@ -1,9 +1,7 @@ import { useCallback } from 'react'; -import { - type RegistrationInExchangeOracleDto, - useExchangeOracleRegistrationMutation, -} from '@/modules/worker/oracle-registration/sevices'; import { useRegisteredOracles } from '@/shared/contexts/registered-oracles'; +import { type RegistrationInExchangeOracleDto } from '../schema'; +import { useExchangeOracleRegistrationMutation } from './use-exchange-oracle-registration-mutation'; export function useOracleRegistration(oracleAddress: string | undefined) { const { setRegisteredOracles } = useRegisteredOracles(); 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 97685e3d18..4a3e3005d6 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 @@ -4,11 +4,11 @@ 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 { useOracleRegistrationFlow } from './hooks'; import { - registrationInExchangeOracleDtoSchema, type RegistrationInExchangeOracleDto, -} from './sevices'; -import { useOracleRegistrationFlow } from './hooks'; + registrationInExchangeOracleDtoSchema, +} from './schema'; function useRegistrationForm(address: string) { return useForm({ 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 a13c018cf5..2e42c48248 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 @@ -4,7 +4,7 @@ import { Navigate, 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 '../jobs-discovery'; +import { useGetOracles } from '../hooks'; import { useIsAlreadyRegistered } from './hooks'; function isAddress(address: string | undefined): address is string { diff --git a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/schema.ts b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/schema.ts new file mode 100644 index 0000000000..a87e92b0d5 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/schema.ts @@ -0,0 +1,18 @@ +/* eslint-disable camelcase */ +import { ethers } from 'ethers'; +import { t } from 'i18next'; +import { z } from 'zod'; + +export const registrationInExchangeOracleDtoSchema = z.object({ + oracle_address: z + .string() + .refine( + (address) => ethers.isAddress(address), + t('validation.invalidOracleAddress') + ), + h_captcha_token: z.string().min(1, t('validation.captcha')).default('token'), +}); + +export type RegistrationInExchangeOracleDto = z.infer< + typeof registrationInExchangeOracleDtoSchema +>; diff --git a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/sevices/index.ts b/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/sevices/index.ts deleted file mode 100644 index aeaed03788..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/oracle-registration/sevices/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './registration-in-exchange-oracles'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/get-kyc-session-id.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-kyc-mutation.ts similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/services/get-kyc-session-id.ts rename to packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-kyc-mutation.ts diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-kyc.ts b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-kyc.ts index 6ab0781f4b..08c93f14ab 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-kyc.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/profile/hooks/use-start-kyc.ts @@ -1,7 +1,7 @@ import { useEffect, useState, useCallback } from 'react'; -import { useKycStartMutation } from '@/modules/worker/services/get-kyc-session-id'; import { useKycErrorNotifications } from '@/modules/worker/hooks/use-kyc-notification'; import { FetchError } from '@/api/fetcher'; +import { useKycStartMutation } from './use-start-kyc-mutation'; export function useStartKyc() { const [isKYCInProgress, setIsKYCInProgress] = useState(false); diff --git a/packages/apps/human-app/frontend/src/modules/worker/reset-password/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/worker/reset-password/hooks/index.ts new file mode 100644 index 0000000000..d92c78112b --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/reset-password/hooks/index.ts @@ -0,0 +1 @@ +export * from './reset-password'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/reset-password.ts b/packages/apps/human-app/frontend/src/modules/worker/reset-password/hooks/reset-password.ts similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/services/reset-password.ts rename to packages/apps/human-app/frontend/src/modules/worker/reset-password/hooks/reset-password.ts diff --git a/packages/apps/human-app/frontend/src/modules/worker/reset-password/index.ts b/packages/apps/human-app/frontend/src/modules/worker/reset-password/index.ts new file mode 100644 index 0000000000..da7b3139be --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/reset-password/index.ts @@ -0,0 +1,2 @@ +export * from './reset-password-success.page'; +export * from './reset-password.page'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/views/reset-password/reset-password-success.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/reset-password/reset-password-success.page.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/views/reset-password/reset-password-success.page.tsx rename to packages/apps/human-app/frontend/src/modules/worker/reset-password/reset-password-success.page.tsx diff --git a/packages/apps/human-app/frontend/src/modules/worker/views/reset-password/reset-password.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/reset-password/reset-password.page.tsx similarity index 96% rename from packages/apps/human-app/frontend/src/modules/worker/views/reset-password/reset-password.page.tsx rename to packages/apps/human-app/frontend/src/modules/worker/reset-password/reset-password.page.tsx index 380848a8dd..a652add11d 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/views/reset-password/reset-password.page.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/reset-password/reset-password.page.tsx @@ -10,16 +10,16 @@ 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 type { ResetPasswordDto } from '@/modules/worker/services/reset-password'; -import { - resetPasswordDtoSchema, - useResetPasswordMutation, -} from '@/modules/worker/services/reset-password'; import { Alert } from '@/shared/components/ui/alert'; import { getErrorMessageForError } from '@/shared/errors'; import { routerPaths } from '@/router/router-paths'; import { HCaptchaForm } from '@/shared/components/hcaptcha'; import { useResetMutationErrors } from '@/shared/hooks/use-reset-mutation-errors'; +import { + type ResetPasswordDto, + resetPasswordDtoSchema, + useResetPasswordMutation, +} from './hooks'; export function ResetPasswordWorkerPage() { const location = useLocation(); diff --git a/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/hooks/index.ts new file mode 100644 index 0000000000..b1db8b482b --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/hooks/index.ts @@ -0,0 +1 @@ +export * from './send-reset-link'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/send-reset-link.ts b/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/hooks/send-reset-link.ts similarity index 57% rename from packages/apps/human-app/frontend/src/modules/worker/services/send-reset-link.ts rename to packages/apps/human-app/frontend/src/modules/worker/send-reset-link/hooks/send-reset-link.ts index 3716e8e0b4..ba28c6ae7e 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/services/send-reset-link.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/hooks/send-reset-link.ts @@ -1,34 +1,10 @@ -/* eslint-disable camelcase -- ... */ import { z } from 'zod'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { useNavigate } from 'react-router-dom'; -import { t } from 'i18next'; import { apiClient } from '@/api/api-client'; import { apiPaths } from '@/api/api-paths'; import { routerPaths } from '@/router/router-paths'; - -export const sendResetLinkEmailDtoSchema = z.object({ - email: z - .string() - .min(1, t('worker.sendResetLinkForm.noEmailError')) - .email(t('worker.sendResetLinkForm.invalidEmailError')), -}); - -export type SendResetLinkEmail = z.infer; - -export const sendResetLinkHcaptchaDtoSchema = z.object({ - h_captcha_token: z.string().min(1, t('validation.captcha')).default('token'), -}); - -export type SendResetLinkHcaptcha = z.infer< - typeof sendResetLinkHcaptchaDtoSchema ->; - -export const sendResetLinkDtoSchema = sendResetLinkEmailDtoSchema.merge( - sendResetLinkHcaptchaDtoSchema -); - -export type SendResetLinkDto = SendResetLinkEmail & SendResetLinkHcaptcha; +import { type SendResetLinkDto } from '../schemas'; const SendResetLinkSuccessResponseSchema = z.unknown(); diff --git a/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/index.ts b/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/index.ts new file mode 100644 index 0000000000..715aa69736 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/index.ts @@ -0,0 +1,2 @@ +export * from './send-reset-link-success.page'; +export * from './send-reset-link.page'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/schemas.ts b/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/schemas.ts new file mode 100644 index 0000000000..a438a00bc9 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/send-reset-link/schemas.ts @@ -0,0 +1,26 @@ +import { t } from 'i18next'; +import { z } from 'zod'; + +const sendResetLinkEmailDtoSchema = z.object({ + email: z + .string() + .min(1, t('worker.sendResetLinkForm.noEmailError')) + .email(t('worker.sendResetLinkForm.invalidEmailError')), +}); + +type SendResetLinkEmail = z.infer; + +export const sendResetLinkHcaptchaDtoSchema = z.object({ + // eslint-disable-next-line camelcase + h_captcha_token: z.string().min(1, t('validation.captcha')).default('token'), +}); + +export type SendResetLinkHcaptcha = z.infer< + typeof sendResetLinkHcaptchaDtoSchema +>; + +export const sendResetLinkDtoSchema = sendResetLinkEmailDtoSchema.merge( + sendResetLinkHcaptchaDtoSchema +); + +export type SendResetLinkDto = SendResetLinkEmail & SendResetLinkHcaptcha; diff --git a/packages/apps/human-app/frontend/src/modules/worker/views/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 similarity index 96% rename from packages/apps/human-app/frontend/src/modules/worker/views/send-reset-link/send-reset-link-success.page.tsx rename to packages/apps/human-app/frontend/src/modules/worker/send-reset-link/send-reset-link-success.page.tsx index 54faa1ebe1..bb959449f3 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/views/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 @@ -8,11 +8,6 @@ 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 type { SendResetLinkHcaptcha } from '@/modules/worker/services/send-reset-link'; -import { - sendResetLinkHcaptchaDtoSchema, - useSendResetLinkMutation, -} from '@/modules/worker/services/send-reset-link'; import { Alert } from '@/shared/components/ui/alert'; import { getErrorMessageForError } from '@/shared/errors'; import { HCaptchaForm } from '@/shared/components/hcaptcha'; @@ -20,6 +15,11 @@ 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'; export function SendResetLinkWorkerSuccessPage() { const { colorPalette, isDarkMode } = useColorMode(); diff --git a/packages/apps/human-app/frontend/src/modules/worker/views/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 similarity index 93% rename from packages/apps/human-app/frontend/src/modules/worker/views/send-reset-link/send-reset-link.page.tsx rename to packages/apps/human-app/frontend/src/modules/worker/send-reset-link/send-reset-link.page.tsx index 5759d3657f..1af8aab18b 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/views/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 @@ -6,17 +6,14 @@ 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 type { SendResetLinkDto } from '@/modules/worker/services/send-reset-link'; -import { - sendResetLinkDtoSchema, - useSendResetLinkMutation, -} from '@/modules/worker/services/send-reset-link'; 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'; 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'; export function SendResetLinkWorkerPage() { const { t } = useTranslation(); diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/get-on-chain-registered-address.ts b/packages/apps/human-app/frontend/src/modules/worker/services/get-on-chain-registered-address.ts deleted file mode 100644 index d4a6f25467..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/services/get-on-chain-registered-address.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* eslint-disable camelcase -- ... */ -import { z } from 'zod'; -import { useQuery } from '@tanstack/react-query'; -import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user'; -import { ethKVStoreGetKycData } from '@/modules/smart-contracts/EthKVStore/eth-kv-store-get-kyc-data'; -import { getContractAddress } from '@/modules/smart-contracts/get-contract-address'; -import { useWalletConnect } from '@/shared/contexts/wallet-connect'; - -export interface RegisterAddressPayload { - address: string; -} - -export const RegisterAddressSuccessSchema = z.object({ - signed_address: z.string(), -}); - -export type RegisterAddressSuccess = z.infer< - typeof RegisterAddressSuccessSchema ->; - -export function useGetOnChainRegisteredAddress() { - const { user } = useAuthenticatedUser(); - const { address } = useWalletConnect(); - - return useQuery({ - queryFn: async () => { - const contractAddress = getContractAddress({ - contractName: 'EthKVStore', - }); - - const registeredAddressOnChain = await ethKVStoreGetKycData({ - contractAddress, - accountAddress: user.wallet_address ?? address ?? '', - kycKey: `KYC-${user.reputation_network}`, - }); - - return registeredAddressOnChain; - }, - retry: 0, - refetchInterval: 0, - refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: false, - queryKey: [user.wallet_address, user.reputation_network, address], - }); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/mocks/available-jobs-table-service-mock.ts b/packages/apps/human-app/frontend/src/modules/worker/services/mocks/available-jobs-table-service-mock.ts deleted file mode 100644 index 71af139f0f..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/services/mocks/available-jobs-table-service-mock.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-disable camelcase -- output from api */ -export interface AvailableJobs { - page: number; - page_size: number; - total_pages: number; - total_results: number; - results: JobsArray[]; -} - -export interface JobsArray { - escrow_address: string; - chain_id: number; - job_type: string; - status: string; -} - -const data: AvailableJobs = { - page: 0, - page_size: 5, - total_pages: 2, - total_results: 7, - results: [ - { - escrow_address: '0x2db00C8A1793424e35f6Cc634Eb13CC174929A4A', - chain_id: 80002, - job_type: 'fortune', - status: 'active', - }, - { - escrow_address: '0x7Cf6978f8699Cf22a121B6332BDF3c5C2F10e3e3', - chain_id: 80002, - job_type: 'fortune', - status: 'active', - }, - { - escrow_address: '0xb389ac3678bF3723863dF92B5D531b0d12e82431', - chain_id: 80002, - job_type: 'fortune', - status: 'active', - }, - { - escrow_address: '0xe9B9b198b093A078Fe8900b703637C26FD2f06a4', - chain_id: 80002, - job_type: 'fortune', - status: 'active', - }, - { - escrow_address: '0x531e2CDB13f2c5606F8C251799f93CBb1219C14C', - chain_id: 80002, - job_type: 'fortune', - status: 'active', - }, - ], -}; - -export function getJobsTableData(): Promise { - return Promise.resolve(data); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/mocks/my-jobs-table-service-mock.ts b/packages/apps/human-app/frontend/src/modules/worker/services/mocks/my-jobs-table-service-mock.ts deleted file mode 100644 index 9505dd28fc..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/services/mocks/my-jobs-table-service-mock.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-disable camelcase -- output from api */ -export interface MyJobs { - page: number; - page_size: number; - total_pages: number; - total_results: number; - results: JobsArray[]; -} - -interface JobsArray { - assignment_id: number; - escrow_address: string; - chain_id: number; - job_type: string; - status: string; - reward_amount: number; - reward_token: string; - created_at: string; - expires_at: string; - url: string; -} - -const data: MyJobs = { - page: 0, - page_size: 5, - total_pages: 1, - total_results: 2, - results: [ - { - assignment_id: 8, - escrow_address: '0x2db00C8A1793424e35f6Cc634Eb13CC174929A4A', - chain_id: 80002, - job_type: 'fortune', - status: 'active', - reward_amount: 14.004735281093245, - reward_token: 'HMT', - created_at: '2024-04-22T14:38:03.956Z', - expires_at: '2024-07-25T06:05:16.000Z', - url: 'http://stg-fortune-exchange-oracle-server.humanprotocol.org', - }, - { - assignment_id: 9, - escrow_address: '0xb389ac3678bF3723863dF92B5D531b0d12e82431', - chain_id: 80002, - job_type: 'fortune', - status: 'active', - reward_amount: 14.550093644402695, - reward_token: 'HMT', - created_at: '2024-04-23T08:24:14.274Z', - expires_at: '2024-07-27T14:25:15.000Z', - url: 'http://stg-fortune-exchange-oracle-server.humanprotocol.org', - }, - ], -}; - -export function getJobsTableData(): Promise { - return Promise.resolve(data); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/sign-in/types.ts b/packages/apps/human-app/frontend/src/modules/worker/services/sign-in/types.ts deleted file mode 100644 index 6f5dbeae11..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/services/sign-in/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { type z } from 'zod'; -import { - type signInDtoSchema, - type signInSuccessResponseSchema, -} from './schema'; - -export type SignInDto = z.infer; - -export type SignInSuccessResponse = z.infer; diff --git a/packages/apps/human-app/frontend/src/router/router-paths.ts b/packages/apps/human-app/frontend/src/router/router-paths.ts index a5094292dd..530a26bc7f 100644 --- a/packages/apps/human-app/frontend/src/router/router-paths.ts +++ b/packages/apps/human-app/frontend/src/router/router-paths.ts @@ -1,6 +1,5 @@ export const routerPaths = { homePage: '/', - playground: '/playground', worker: { signIn: '/worker/sign-in', signUp: '/worker/sign-up', @@ -12,7 +11,7 @@ export const routerPaths = { verifyEmail: '/worker/verify-email', profile: '/worker/profile', jobsDiscovery: '/worker/jobs-discovery', - jobs: '/worker/jobs', + jobs: '/worker/jobs-discovery', HcaptchaLabeling: '/worker/hcaptcha-labeling', enableLabeler: '/worker/enable-labeler', registrationInExchangeOracle: '/worker/registration-in-exchange-oracle', diff --git a/packages/apps/human-app/frontend/src/router/routes.tsx b/packages/apps/human-app/frontend/src/router/routes.tsx index d914cdbee9..d6d7f85ee9 100644 --- a/packages/apps/human-app/frontend/src/router/routes.tsx +++ b/packages/apps/human-app/frontend/src/router/routes.tsx @@ -1,11 +1,6 @@ import type { RouteProps } from 'react-router-dom'; import { t } from 'i18next'; import { routerPaths } from '@/router/router-paths'; -import { SendResetLinkWorkerSuccessPage } from '@/modules/worker/views/send-reset-link/send-reset-link-success.page'; -import { ResetPasswordWorkerPage } from '@/modules/worker/views/reset-password/reset-password.page'; -import { SendResetLinkWorkerPage } from '@/modules/worker/views/send-reset-link/send-reset-link.page'; -import { ResetPasswordWorkerSuccessPage } from '@/modules/worker/views/reset-password/reset-password-success.page'; -import { JobsPage } from '@/modules/worker/views/jobs/jobs.page'; import { env } from '@/shared/env'; import { RegistrationPage } from '@/modules/worker/oracle-registration'; import { @@ -14,7 +9,6 @@ import { WorkHeaderIcon, } from '@/shared/components/ui/icons'; import type { PageHeaderProps } from '@/shared/components/layout/protected/page-header'; -import { Playground } from '@/modules/playground/views/playground.page'; import { HcaptchaLabelingPage, UserStatsAccordion, @@ -37,16 +31,21 @@ import { EditExistingKeysSuccessPage, SetUpOperatorPage, } from '@/modules/signup/operator'; +import { JobsPage } from '@/modules/worker/jobs'; +import { + ResetPasswordWorkerPage, + ResetPasswordWorkerSuccessPage, +} from '@/modules/worker/reset-password'; +import { + SendResetLinkWorkerPage, + SendResetLinkWorkerSuccessPage, +} from '@/modules/worker/send-reset-link'; export const unprotectedRoutes: RouteProps[] = [ { path: routerPaths.homePage, element: , }, - { - path: routerPaths.playground, - element: , - }, { path: routerPaths.worker.signIn, element: , diff --git a/packages/apps/human-app/frontend/src/shared/components/data-entry/form-example.tsx b/packages/apps/human-app/frontend/src/shared/components/data-entry/form-example.tsx deleted file mode 100644 index 45d053fb78..0000000000 --- a/packages/apps/human-app/frontend/src/shared/components/data-entry/form-example.tsx +++ /dev/null @@ -1,197 +0,0 @@ -import { useState } from 'react'; -import type { SubmitHandler } from 'react-hook-form'; -import { useForm, FormProvider } from 'react-hook-form'; -import Grid from '@mui/material/Grid'; -import Box from '@mui/material/Box'; -import Typography from '@mui/material/Typography'; -import Stack from '@mui/material/Stack'; -import { Input } from '@/shared/components/data-entry/input'; -import { Select } from '@/shared/components/data-entry/select'; -import { RadioButton } from '@/shared/components/data-entry/radio-button'; -import { Checkbox } from '@/shared/components/data-entry/checkbox'; -import { Slider } from '@/shared/components/data-entry/slider'; -import { MultiSelect } from '@/shared/components/data-entry/multi-select'; -import { Password } from '@/shared/components/data-entry/password/password'; -import { PercentsInputMask, HumanCurrencyInputMask } from './input-masks'; - -export interface Inputs { - name: string; - surname: string; - email: string; - firstCheckbox: boolean; - slider: number; - month: string; -} - -const names = [ - 'Oliver Hansen', - 'Van Henry', - 'April Tucker', - 'Ralph Hubbard', - 'Omar Alexander', - 'Carlos Abbott', - 'Miriam Wagner', - 'Bradley Wilkerson', - 'Virginia Andrews', - 'Kelly Snyder', -]; - -const accounts = [ - { - id: 1, - name: 'PL76114011245044546199764000', - value: 'PL76114011245044546199764000', - }, - { - id: 2, - name: 'PL76114011245044546199761111', - value: 'PL76114011245044546199761111', - }, - { - id: 2, - name: 'PL76114011245044546199764117', - value: 'PL76114011245044546199764117', - }, -]; - -const MIN = 3000; -const MAX = 50000; - -function CustomMarks({ min, max }: { min: number; max: number }) { - return ( - - min. {min} PLN - max. {max} PLN - - ); -} - -export function FormExample() { - const [values, setValues] = useState(); - const methods = useForm({ - defaultValues: { - name: '', - surname: '', - email: '', - }, - }); - - const onSubmit: SubmitHandler = (data) => { - const formData = { - ...data, - }; - setValues(formData); - }; - - return ( - <> - -
void methods.handleSubmit(onSubmit)(event)}> -

Inputs

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- {values ? ( - - Name: {values.name} - Surname: {values.surname} - Email: {values.email} - Slider: {values.slider} - Month: {values.month} - - ) : null} - - ); -} diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/modal/modal-content.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/modal/modal-content.tsx index 26d572b36b..41d5835039 100644 --- a/packages/apps/human-app/frontend/src/shared/components/ui/modal/modal-content.tsx +++ b/packages/apps/human-app/frontend/src/shared/components/ui/modal/modal-content.tsx @@ -1,11 +1,9 @@ import type { ComponentType } from 'react'; -import { ModalExample } from '@/modules/playground/components/modal-example/modal-example'; import { WalletConnectModal } from '@/modules/auth-web3/components/wallet-connect-modal'; import { ExpirationModal } from '@/modules/auth/components/expiration-modal'; import { ModalType } from './modal.store'; const MODAL_COMPONENTS_MAP: Record = { - [ModalType.MODAL_EXAMPLE]: ModalExample, [ModalType.WALLET_CONNECT]: WalletConnectModal, [ModalType.EXPIRATION_MODAL]: ExpirationModal, }; @@ -14,7 +12,7 @@ interface ModalContent { modalType: ModalType; } -export function ModalContent({ modalType }: ModalContent) { +export function ModalContent({ modalType }: Readonly) { const Content = MODAL_COMPONENTS_MAP[modalType]; return ; } diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/modal/modal.store.ts b/packages/apps/human-app/frontend/src/shared/components/ui/modal/modal.store.ts index 2424ea9eb8..fe5ae32916 100644 --- a/packages/apps/human-app/frontend/src/shared/components/ui/modal/modal.store.ts +++ b/packages/apps/human-app/frontend/src/shared/components/ui/modal/modal.store.ts @@ -3,7 +3,6 @@ import type { ReactNode } from 'react'; import type { DialogProps as DialogMuiProps } from '@mui/material/Dialog'; export enum ModalType { - MODAL_EXAMPLE = 'MODAL_EXAMPLE', WALLET_CONNECT = 'WALLET_CONNECT', EXPIRATION_MODAL = 'EXPIRATION_MODAL', } diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/table/table-header-cell.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/table/table-header-cell.tsx index e5f001079b..fb61df67d0 100644 --- a/packages/apps/human-app/frontend/src/shared/components/ui/table/table-header-cell.tsx +++ b/packages/apps/human-app/frontend/src/shared/components/ui/table/table-header-cell.tsx @@ -1,8 +1,7 @@ import React, { forwardRef, useState } from 'react'; import Popover from '@mui/material/Popover'; import type { TableCellBaseProps } from '@mui/material/TableCell/TableCell'; -import type { IconType } from '@/modules/worker/components/jobs/text-header-with-icon'; -import { TextHeaderWithIcon } from '@/modules/worker/components/jobs/text-header-with-icon'; +import { type IconType, TextHeaderWithIcon } from '../text-header-with-icon'; type CommonProps = TableCellBaseProps & { popoverContent: React.ReactElement; diff --git a/packages/apps/human-app/frontend/src/modules/worker/components/jobs/text-header-with-icon.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/text-header-with-icon.tsx similarity index 100% rename from packages/apps/human-app/frontend/src/modules/worker/components/jobs/text-header-with-icon.tsx rename to packages/apps/human-app/frontend/src/shared/components/ui/text-header-with-icon.tsx diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/ui-example.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/ui-example.tsx deleted file mode 100644 index f3eb5d962e..0000000000 --- a/packages/apps/human-app/frontend/src/shared/components/ui/ui-example.tsx +++ /dev/null @@ -1,259 +0,0 @@ -import Grid from '@mui/material/Grid'; -import Stack from '@mui/material/Stack'; -import Typography from '@mui/material/Typography'; -import { Button } from '@/shared/components/ui/button'; -import { Loader } from '@/shared/components/ui/loader'; -import { - ModalType, - useModalStore, -} from '@/shared/components/ui/modal/modal.store'; -import { - HomepageLogoIcon, - HomepageUserIcon, - HomepageWorkIcon, - HumanLogoIcon, - ChatIcon, - HandIcon, - RefreshIcon, - UserOutlinedIcon, - WorkIcon, - HumanLogoNavbarIcon, - HelpIcon, - ProfileIcon, - CheckmarkIcon, - LockerIcon, - FiltersButtonIcon, - SortArrow, -} from '@/shared/components/ui/icons'; -import { TableExample } from '@/modules/playground/components/table-example/table-example'; -import { Alert } from '@/shared/components/ui/alert'; -import { ConnectWalletBtn } from '@/shared/components/ui/connect-wallet-btn'; -import { useColorMode } from '@/shared/contexts/color-mode'; - -export function UiExample() { - const { colorPalette } = useColorMode(); - const { openModal } = useModalStore(); - return ( -
- - Fonts - - - - IMPORTANT - To use Header 1 - 5 you have to add into Typography a prop - component - - - - - H1 / Inter Extrabold 80 - - - - H2 / Inter Semibold 60 - - - - H3 / Inter Regular 48 - - - - H4 / Inter Semibold 34 - - - - H5 / Inter Regular 24 - - - - H6 / Inter Medium 20 - - - - Subtitle 1 / Inter Regular 16 - - - - Subtitle 2 / Inter Semibold 14 - - - Body 1 / Inter Regular 16 - - Body 1 / Inter Regular 14 - - Body 3 / Inter Medium 16 - - - Button large / Inter Semibold 15 - - - - Button medium / Inter Semibold 14 - - - - Button small / Inter Semibold 13 - - - Caption / Inter Regular 12 - Overline / Inter Regular 12 - - Avatar Letter / Inter Regular 20 - - - Input Label / Inter Regular 12 - - - Helper Text / Inter Regular 12 - - - Input Text / Inter Regular 16 - - Tooltip / Inter Medium 12 - - Input Under line / Inter Semibold 12 - - - -

Buttons

- - - - - - - - -

Button sizes

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

Connect wallet button

- - -

Loader

- - - - - - -

Icons

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

Modal

- - - -

Table

- - -

Alert

- - - An error has occurred, please try again. - - - Your password has been successfully updated! - - -
- ); -} diff --git a/packages/apps/human-app/frontend/src/shared/contexts/browser-auth-provider.ts b/packages/apps/human-app/frontend/src/shared/contexts/browser-auth-provider.ts index 2e559e097c..ce781b4439 100644 --- a/packages/apps/human-app/frontend/src/shared/contexts/browser-auth-provider.ts +++ b/packages/apps/human-app/frontend/src/shared/contexts/browser-auth-provider.ts @@ -1,6 +1,6 @@ /* eslint-disable camelcase -- ...*/ -import { type SignInSuccessResponse } from '@/modules/worker/services/sign-in/types'; import type { BrowserAuthProvider } from '@/shared/types/browser-auth-provider'; +import { type AuthTokensSuccessResponse } from '../schemas'; const accessTokenKey = btoa('access_token'); const refreshTokenKey = btoa('refresh_token'); @@ -11,7 +11,7 @@ const browserAuthProvider: BrowserAuthProvider = { isAuthenticated: false, authType: 'web2', signIn( - { access_token, refresh_token }: SignInSuccessResponse, + { access_token, refresh_token }: AuthTokensSuccessResponse, authType, signOutSubscription ) { diff --git a/packages/apps/human-app/frontend/src/shared/hooks/index.ts b/packages/apps/human-app/frontend/src/shared/hooks/index.ts new file mode 100644 index 0000000000..2818199440 --- /dev/null +++ b/packages/apps/human-app/frontend/src/shared/hooks/index.ts @@ -0,0 +1,8 @@ +export * from './use-combine-pages'; +export * from './use-count-down'; +export * from './use-handle-main-nav-icon-click'; +export * from './use-is-hcaptcha-labeling-page'; +export * from './use-notification'; +export * from './use-is-mobile'; +export * from './use-reset-mutation-errors'; +export * from './use-web3-provider'; diff --git a/packages/apps/human-app/frontend/src/shared/hooks/use-combine-pages.ts b/packages/apps/human-app/frontend/src/shared/hooks/use-combine-pages.ts new file mode 100644 index 0000000000..74a5ac32be --- /dev/null +++ b/packages/apps/human-app/frontend/src/shared/hooks/use-combine-pages.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from 'react'; + +export function useCombinePages( + tableData: { pages: { results: T[] }[] } | undefined, + page: number +) { + const [allPages, setAllPages] = useState([]); + + useEffect(() => { + if (!tableData) return; + const pagesFromRes = tableData.pages.flatMap((pages) => pages.results); + + if (page === 0) { + setAllPages(pagesFromRes); + } else { + setAllPages((state) => [...state, ...pagesFromRes]); + } + }, [tableData, page]); + + return allPages; +} diff --git a/packages/apps/human-app/frontend/src/shared/schemas/auth-tokens-schema.ts b/packages/apps/human-app/frontend/src/shared/schemas/auth-tokens-schema.ts new file mode 100644 index 0000000000..3cb24a0b99 --- /dev/null +++ b/packages/apps/human-app/frontend/src/shared/schemas/auth-tokens-schema.ts @@ -0,0 +1,12 @@ +import { z } from 'zod'; + +export const authTokensSuccessResponseSchema = z.object({ + // eslint-disable-next-line camelcase -- data from api + access_token: z.string(), + // eslint-disable-next-line camelcase -- data from api + refresh_token: z.string(), +}); + +export type AuthTokensSuccessResponse = z.infer< + typeof authTokensSuccessResponseSchema +>; diff --git a/packages/apps/human-app/frontend/src/shared/schemas/index.ts b/packages/apps/human-app/frontend/src/shared/schemas/index.ts index adde22820b..73ec748501 100644 --- a/packages/apps/human-app/frontend/src/shared/schemas/index.ts +++ b/packages/apps/human-app/frontend/src/shared/schemas/index.ts @@ -1,2 +1,3 @@ export * from './validate-address-schema'; export * from './url-domain-schema'; +export * from './auth-tokens-schema'; diff --git a/packages/apps/human-app/frontend/src/shared/types/browser-auth-provider.ts b/packages/apps/human-app/frontend/src/shared/types/browser-auth-provider.ts index 61413cfe5d..297669b6c9 100644 --- a/packages/apps/human-app/frontend/src/shared/types/browser-auth-provider.ts +++ b/packages/apps/human-app/frontend/src/shared/types/browser-auth-provider.ts @@ -1,6 +1,6 @@ -import type { SignInSuccessResponse } from '@/modules/worker/services/sign-in/types'; import type { Web3UserData } from '@/modules/auth-web3/context/web3-auth-context'; import type { UserData } from '@/modules/auth/context/auth-context'; +import { type AuthTokensSuccessResponse } from '../schemas'; export type AuthType = 'web2' | 'web3'; type SubscriptionCallback = () => void; @@ -8,7 +8,7 @@ export interface BrowserAuthProvider { isAuthenticated: boolean; authType: AuthType; signIn: ( - singInSuccessData: SignInSuccessResponse, + singInSuccessData: AuthTokensSuccessResponse, authType: AuthType, signOutSubscription?: SubscriptionCallback ) => void; diff --git a/packages/apps/job-launcher/client/package.json b/packages/apps/job-launcher/client/package.json index 07249cbce7..0782ecf949 100644 --- a/packages/apps/job-launcher/client/package.json +++ b/packages/apps/job-launcher/client/package.json @@ -14,9 +14,9 @@ "@reduxjs/toolkit": "^2.5.0", "@stripe/react-stripe-js": "^3.0.0", "@stripe/stripe-js": "^4.2.0", - "@tanstack/query-sync-storage-persister": "^5.59.0", - "@tanstack/react-query": "^5.60.5", - "@tanstack/react-query-persist-client": "^5.66.9", + "@tanstack/query-sync-storage-persister": "^5.68.0", + "@tanstack/react-query": "^5.67.2", + "@tanstack/react-query-persist-client": "^5.67.2", "axios": "^1.1.3", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.12", diff --git a/packages/apps/job-launcher/client/src/App.tsx b/packages/apps/job-launcher/client/src/App.tsx index 603fbe19b8..c7dbc804c7 100644 --- a/packages/apps/job-launcher/client/src/App.tsx +++ b/packages/apps/job-launcher/client/src/App.tsx @@ -1,6 +1,5 @@ import { BrowserRouter, Navigate, Route, Routes } from 'react-router-dom'; import { ProtectedRoute } from './components/ProtectedRoute'; -import { IS_MAINNET } from './constants/chains'; import './index.css'; import Layout from './layouts'; import Dashboard from './pages/Dashboard'; @@ -74,18 +73,16 @@ export default function App() { } /> - {!IS_MAINNET && ( - <> - - - - } - /> - - )} + <> + + + + } + /> + } /> diff --git a/packages/apps/job-launcher/client/src/components/Headers/AuthHeader.tsx b/packages/apps/job-launcher/client/src/components/Headers/AuthHeader.tsx index f5bb59585c..c6db6b328b 100644 --- a/packages/apps/job-launcher/client/src/components/Headers/AuthHeader.tsx +++ b/packages/apps/job-launcher/client/src/components/Headers/AuthHeader.tsx @@ -137,9 +137,6 @@ export const AuthHeader = () => { > - {/* - Tony Wen - */} {user?.email} @@ -158,8 +155,8 @@ export const AuthHeader = () => { Balance - {user?.balance?.amount || 0}{' '} - {user?.balance?.currency?.toUpperCase()} + ~{user?.balance?.totalUsdAmount.toFixed(2) || 0} + {' USD'} diff --git a/packages/apps/job-launcher/client/src/components/Icons/chains.tsx b/packages/apps/job-launcher/client/src/components/Icons/chains.tsx index 10b628ef7a..45909024a7 100644 --- a/packages/apps/job-launcher/client/src/components/Icons/chains.tsx +++ b/packages/apps/job-launcher/client/src/components/Icons/chains.tsx @@ -2,6 +2,7 @@ import { ChainId } from '@human-protocol/sdk'; import { ReactElement } from 'react'; import { BinanceSmartChainIcon } from './BinanceSmartChainIcon'; +import { DollarSignIcon } from './DollarSignIcon'; import { EthereumIcon } from './EthereumIcon'; import { HumanIcon } from './HumanIcon'; import { PolygonIcon } from './PolygonIcon'; @@ -18,4 +19,6 @@ export const CHAIN_ICONS: { [chainId in ChainId]?: ReactElement } = { export const TOKEN_ICONS: Record = { HMT: , + USDC: , + USDT: , }; diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/CreateJob.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/CreateJob.tsx index c609b8e3b1..36b4732a7a 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/CreateJob.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/CreateJob.tsx @@ -13,7 +13,6 @@ import { HCaptchaJobRequestForm } from './HCaptchaJobRequestForm'; export const CreateJob = () => { const { payMethod, jobRequest, updateJobRequest } = useCreateJobPageUI(); const { chainId } = jobRequest; - console.log(jobRequest); const { chain } = useAccount(); const { switchChainAsync } = useSwitchChain(); diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx index a08ffdbbee..0777ddf7e4 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/CryptoPayForm.tsx @@ -15,6 +15,7 @@ import { TextField, Typography, } from '@mui/material'; +import { Decimal } from 'decimal.js'; import { ethers } from 'ethers'; import { useEffect, useMemo, useState } from 'react'; import { Address } from 'viem'; @@ -25,8 +26,6 @@ import { usePublicClient, } from 'wagmi'; import { TokenSelect } from '../../../components/TokenSelect'; -import { NETWORK_TOKENS } from '../../../constants/chains'; -import { useTokenRate } from '../../../hooks/useTokenRate'; import { useCreateJobPageUI } from '../../../providers/CreateJobPageUIProvider'; import * as jobService from '../../../services/job'; import * as paymentService from '../../../services/payment'; @@ -45,8 +44,9 @@ export const CryptoPayForm = ({ const { isConnected } = useAccount(); const { chain } = useAccount(); const { jobRequest, goToPrevStep } = useCreateJobPageUI(); - const [tokenAddress, setTokenAddress] = useState(); - const [tokenSymbol, setTokenSymbol] = useState(); + const [paymentTokenAddress, setPaymentTokenAddress] = useState(); + const [paymentTokenSymbol, setPaymentTokenSymbol] = useState(); + const [fundTokenSymbol, setFundTokenSymbol] = useState(); const [payWithAccountBalance, setPayWithAccountBalance] = useState(false); const [amount, setAmount] = useState(); const [isLoading, setIsLoading] = useState(false); @@ -55,7 +55,10 @@ export const CryptoPayForm = ({ const { data: signer } = useWalletClient(); const publicClient = usePublicClient(); const { user } = useAppSelector((state) => state.auth); - const { data: rate } = useTokenRate('hmt', 'usd'); + const [paymentTokenRate, setPaymentTokenRate] = useState(0); + const [fundTokenRate, setFundTokenRate] = useState(0); + const [decimals, setDecimals] = useState(6); + const [tokenDecimals, setTokenDecimals] = useState(18); useEffect(() => { const fetchJobLauncherData = async () => { @@ -68,6 +71,41 @@ export const CryptoPayForm = ({ fetchJobLauncherData(); }, []); + useEffect(() => { + const fetchRates = async () => { + if (paymentTokenSymbol && fundTokenSymbol) { + if (paymentTokenSymbol === fundTokenSymbol) { + const rate = await paymentService.getRate(paymentTokenSymbol, 'usd'); + setPaymentTokenRate(rate); + setFundTokenRate(rate); + } else { + const paymentRate = await paymentService.getRate( + paymentTokenSymbol, + 'usd', + ); + const fundRate = await paymentService.getRate(fundTokenSymbol, 'usd'); + setPaymentTokenRate(paymentRate); + setFundTokenRate(fundRate); + } + } + }; + + fetchRates(); + }, [paymentTokenSymbol, fundTokenSymbol]); + + useEffect(() => { + if (amount) { + const [integerPart, decimalPart] = amount.split('.'); + if (decimalPart && decimalPart.length > decimals) { + setAmount(`${integerPart}.${decimalPart.slice(0, decimals)}`); + } + } + }, [decimals, amount]); + + useEffect(() => { + setDecimals(Math.min(tokenDecimals, 6)); + }, [tokenDecimals]); + const { data: jobLauncherFee } = useReadContract({ address: NETWORKS[jobRequest.chainId!]?.kvstoreAddress as Address, abi: KVStoreABI, @@ -80,44 +118,86 @@ export const CryptoPayForm = ({ }, }); - const fundAmount = useMemo(() => { - if (amount && rate) return Number(amount) * rate; + const minFeeToken = useMemo(() => { + if (minFee && paymentTokenRate) + return new Decimal(minFee).div(paymentTokenRate).toNumber(); return 0; - }, [amount, rate]); - const feeAmount = - fundAmount === 0 - ? 0 - : Math.max(minFee, fundAmount * (Number(jobLauncherFee) / 100)); - const totalAmount = fundAmount + feeAmount; - const accountAmount = user?.balance ? Number(user?.balance?.amount) : 0; + }, [minFee, paymentTokenRate]); + + const feeAmount = useMemo(() => { + if (!amount) return 0; + const amountDecimal = new Decimal(amount); + const feeDecimal = new Decimal(jobLauncherFee as string).div(100); + return Number( + Decimal.max(minFeeToken, amountDecimal.mul(feeDecimal)).toFixed(decimals), + ); + }, [amount, jobLauncherFee, minFeeToken, decimals]); + + const totalAmount = useMemo(() => { + if (!amount) return 0; + return Number( + new Decimal(amount).plus(feeAmount).toNumber().toFixed(decimals), + ); + }, [amount, decimals, feeAmount]); + + const totalUSDAmount = useMemo(() => { + if (!totalAmount || !paymentTokenRate) return 0; + return new Decimal(totalAmount).mul(paymentTokenRate).toNumber(); + }, [totalAmount, paymentTokenRate]); + + const conversionRate = useMemo(() => { + if (paymentTokenRate && fundTokenRate) { + return new Decimal(paymentTokenRate).div(fundTokenRate).toNumber(); + } + return 1; + }, [paymentTokenRate, fundTokenRate]); + + const fundAmount = useMemo(() => { + if (!amount || !conversionRate) return 0; + return Number(new Decimal(amount).mul(conversionRate)); + }, [amount, conversionRate]); + + const currentBalance = useMemo(() => { + return ( + user?.balance?.balances.find( + (balance) => balance.currency === paymentTokenSymbol, + )?.amount ?? 0 + ); + }, [user, paymentTokenSymbol]); const balancePayAmount = useMemo(() => { if (!payWithAccountBalance) return 0; - if (totalAmount < accountAmount) return totalAmount; - return accountAmount; - }, [payWithAccountBalance, totalAmount, accountAmount]); + const totalAmountDecimal = new Decimal(totalAmount); + if (totalAmountDecimal.lessThan(currentBalance)) return totalAmountDecimal; + return currentBalance; + }, [payWithAccountBalance, totalAmount, currentBalance]); const walletPayAmount = useMemo(() => { if (!payWithAccountBalance) return totalAmount; - if (totalAmount < accountAmount) return 0; - return totalAmount - accountAmount; - }, [payWithAccountBalance, totalAmount, accountAmount]); + const totalAmountDecimal = new Decimal(totalAmount); + if (totalAmountDecimal.lessThan(currentBalance)) return 0; + return Number(totalAmountDecimal.minus(currentBalance)); + }, [payWithAccountBalance, totalAmount, currentBalance]); const handlePay = async () => { - if (signer && tokenAddress && amount && jobRequest.chainId && tokenSymbol) { + if ( + signer && + paymentTokenAddress && + amount && + jobRequest.chainId && + paymentTokenSymbol && + fundTokenSymbol + ) { setIsLoading(true); try { if (walletPayAmount > 0) { - // send HMT token to operator and retrieve transaction hash - const tokenAmount = walletPayAmount / rate; - const hash = await signer.writeContract({ - address: tokenAddress as Address, + address: paymentTokenAddress as Address, abi: HMTokenABI, functionName: 'transfer', args: [ jobLauncherAddress, - ethers.parseUnits(tokenAmount.toString(), 18), + ethers.parseUnits(walletPayAmount.toString(), tokenDecimals), ], }); @@ -146,17 +226,17 @@ export const CryptoPayForm = ({ await jobService.createFortuneJob( chainId, fortuneRequest, - tokenSymbol, + paymentTokenSymbol, Number(amount), - tokenSymbol, + fundTokenSymbol, ); } else if (jobType === JobType.CVAT && cvatRequest) { await jobService.createCvatJob( chainId, cvatRequest, - tokenSymbol, + paymentTokenSymbol, Number(amount), - tokenSymbol, + fundTokenSymbol, ); } else if (jobType === JobType.HCAPTCHA && hCaptchaRequest) { await jobService.createHCaptchaJob(chainId, hCaptchaRequest); @@ -225,25 +305,47 @@ export const CryptoPayForm = ({ )} { - const symbol = e.target.value as string; - setTokenSymbol(symbol); - setTokenAddress( - NETWORK_TOKENS[ - jobRequest.chainId! as keyof typeof NETWORK_TOKENS - ]?.[symbol.toLowerCase()], - ); + value={paymentTokenSymbol} + label={'Payment token'} + labelId={'payment-token'} + onTokenChange={(symbol, address, decimals) => { + setPaymentTokenSymbol(symbol); + setPaymentTokenAddress(address); + setTokenDecimals(decimals); + if (amount) { + const maxDecimals = Math.min(decimals, 6); + const [integerPart, decimalPart] = amount.split('.'); + if (decimalPart && decimalPart.length > maxDecimals) { + setAmount( + `${integerPart}.${decimalPart.slice(0, maxDecimals)}`, + ); + } + } }} /> setAmount(e.target.value as string)} + onChange={(e) => { + let value = e.target.value; + const regex = new RegExp(`^\\d*\\.?\\d{0,${decimals}}$`); + if (regex.test(value)) { + setAmount(value); + } + }} placeholder="Amount" /> + { + setFundTokenSymbol(symbol); + }} + /> @@ -259,51 +361,30 @@ export const CryptoPayForm = ({ borderBottom: '1px solid #E5E7EB', }} > - Account Balance + Balance - ~ {user?.balance?.amount?.toFixed(2) ?? '0'}{' '} - {user?.balance?.currency?.toUpperCase() ?? 'USD'} + {paymentTokenSymbol + ? `${Number(currentBalance?.toFixed(6))} ${paymentTokenSymbol?.toUpperCase()}` + : ''} - Fund Amount - - ~ {fundAmount?.toFixed(2)} USD - - - - Fees - - ({Number(jobLauncherFee)}%) {feeAmount?.toFixed(2)} USD - - - - Payment method - Balance + Amount - {balancePayAmount.toFixed(2)} USD + {paymentTokenSymbol + ? `${amount} ${paymentTokenSymbol?.toUpperCase()}` + : ''} - Crypto Wallet + Fee - {walletPayAmount.toFixed(2)} USD + ({Number(jobLauncherFee)}%){' '} + {paymentTokenSymbol + ? `${Number(feeAmount.toFixed(6))} ${paymentTokenSymbol?.toUpperCase()}` + : ''} - {/* + Total payment + + {paymentTokenSymbol + ? `${Number(totalAmount?.toFixed(6))} ${paymentTokenSymbol?.toUpperCase()} (~${totalUSDAmount.toFixed(2)} USD)` + : ''} + + + + + + Payment method + + - Fees - (3.1%) 9.3 USD - */} + Balance + + {paymentTokenSymbol + ? `${Number(balancePayAmount?.toFixed(6))} ${paymentTokenSymbol?.toUpperCase()}` + : ''} + + - Total - {totalAmount?.toFixed(2)} USD + Crypto Wallet + + {paymentTokenSymbol + ? `${Number(walletPayAmount?.toFixed(6))} ${paymentTokenSymbol?.toUpperCase()}` + : ''} +
+ + Fund Amount + + {fundTokenSymbol && fundAmount + ? `${Number(fundAmount?.toFixed(6))} ${fundTokenSymbol?.toUpperCase()}` + : ''} + + @@ -354,8 +478,8 @@ export const CryptoPayForm = ({ onClick={handlePay} disabled={ !isConnected || - !tokenAddress || - !tokenSymbol || + !paymentTokenAddress || + !fundTokenSymbol || !amount || jobRequest.chainId !== chain?.id } diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/CvatJobRequestForm.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/CvatJobRequestForm.tsx index 5b9100b225..e8ae3643e7 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/CvatJobRequestForm.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/CvatJobRequestForm.tsx @@ -199,6 +199,9 @@ export const CvatJobRequestForm = () => { const dataRegions = values.dataProvider === StorageProviders.AWS ? AWSRegions : GCSRegions; + const bpRegions = + values.bpProvider === StorageProviders.AWS ? AWSRegions : GCSRegions; + const gtRegions = values.gtProvider === StorageProviders.AWS ? AWSRegions : GCSRegions; @@ -469,7 +472,11 @@ export const CvatJobRequestForm = () => { } onBlur={handleBlur} > - AWS + {Object.values(StorageProviders).map((provider) => ( + + {provider.toUpperCase()} + + ))} @@ -577,7 +584,11 @@ export const CvatJobRequestForm = () => { error={touched.bpProvider && Boolean(errors.bpProvider)} onBlur={handleBlur} > - AWS + {Object.values(StorageProviders).map((provider) => ( + + {provider.toUpperCase()} + + ))} @@ -597,7 +608,7 @@ export const CvatJobRequestForm = () => { error={touched.bpRegion && Boolean(errors.bpRegion)} onBlur={handleBlur} > - {Object.values(dataRegions).map((region) => ( + {Object.values(bpRegions).map((region) => ( {region} @@ -688,7 +699,11 @@ export const CvatJobRequestForm = () => { error={touched.gtProvider && Boolean(errors.gtProvider)} onBlur={handleBlur} > - AWS + {Object.values(StorageProviders).map((provider) => ( + + {provider.toUpperCase()} + + ))} diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx index 82330497a1..30644e017a 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/FiatPayForm.tsx @@ -16,8 +16,8 @@ import { Typography, } from '@mui/material'; import { useElements, useStripe } from '@stripe/react-stripe-js'; +import { Decimal } from 'decimal.js'; import { useEffect, useMemo, useState } from 'react'; - import { Address } from 'viem'; import { useReadContract } from 'wagmi'; import AddCardModal from '../../../components/CreditCard/AddCardModal'; @@ -40,6 +40,7 @@ import { getOperatorAddress, getUserBillingInfo, getUserCards, + getRate, } from '../../../services/payment'; import { useAppDispatch, useAppSelector } from '../../../state'; import { fetchUserBalanceAsync } from '../../../state/auth/reducer'; @@ -81,10 +82,30 @@ export const FiatPayForm = ({ const [totalAmount, setTotalAmount] = useState(0); const [balancePayAmount, setBalancePayAmount] = useState(0); const [creditCardPayAmount, setCreditCardPayAmount] = useState(0); - const [accountAmount] = useState( - user?.balance ? Number(user?.balance?.amount) : 0, - ); - const [tokenAddress, setTokenAddress] = useState(); + const [tokenSymbol, setTokenSymbol] = useState(); + const [tokenRate, setTokenRate] = useState(0); + + const currentBalance = useMemo(() => { + return ( + user?.balance?.balances.find((balance) => balance.currency === 'usd') + ?.amount ?? 0 + ); + }, [user]); + + useEffect(() => { + const fetchRates = async () => { + if (tokenSymbol) { + const rate = await getRate('usd', tokenSymbol); + setTokenRate(rate); + } + }; + + fetchRates(); + }, [tokenSymbol]); + + const handleTokenChange = (symbol: string, address: string) => { + setTokenSymbol(symbol); + }; useEffect(() => { const fetchJobLauncherData = async () => { @@ -121,6 +142,7 @@ export const FiatPayForm = ({ } setLoadingInitialData(false); }; + const { data: jobLauncherFee, error, @@ -148,28 +170,40 @@ export const FiatPayForm = ({ }, [isError, error, showError]); useMemo(() => { - setFundAmount(amount ? Number(amount) : 0); - if (Number(jobLauncherFee) >= 0) - setFeeAmount( - Math.max(minFee, fundAmount * (Number(jobLauncherFee) / 100)), - ); - setTotalAmount(fundAmount + feeAmount); - if (!payWithAccountBalance) setBalancePayAmount(0); - else if (totalAmount < accountAmount) setBalancePayAmount(totalAmount); - else setBalancePayAmount(accountAmount); - - if (!payWithAccountBalance) setCreditCardPayAmount(totalAmount); - else if (totalAmount < accountAmount) setCreditCardPayAmount(0); - else setCreditCardPayAmount(totalAmount - accountAmount); + const amountDecimal = new Decimal(amount || 0); + const tokenRateDecimal = new Decimal(tokenRate || 0); + const jobLauncherFeeDecimal = new Decimal( + (jobLauncherFee as string) || 0, + ).div(100); + const minFeeDecimal = new Decimal(minFee || 0); + const fundAmountDecimal = amountDecimal.mul(tokenRateDecimal); + setFundAmount(fundAmountDecimal.toNumber()); + + const feeAmountDecimal = Decimal.max( + minFeeDecimal, + amountDecimal.mul(jobLauncherFeeDecimal), + ); + setFeeAmount(feeAmountDecimal.toNumber()); + + const totalAmountDecimal = amountDecimal.plus(feeAmountDecimal); + setTotalAmount(totalAmountDecimal.toNumber()); + + const balancePayAmountDecimal = payWithAccountBalance + ? Decimal.min(totalAmountDecimal, new Decimal(currentBalance)) + : new Decimal(0); + setBalancePayAmount(balancePayAmountDecimal.toNumber()); + + const creditCardPayAmountDecimal = totalAmountDecimal.minus( + balancePayAmountDecimal, + ); + setCreditCardPayAmount(creditCardPayAmountDecimal.toNumber()); }, [ - accountAmount, + currentBalance, amount, - feeAmount, - fundAmount, jobLauncherFee, minFee, payWithAccountBalance, - totalAmount, + tokenRate, ]); const handleSuccessAction = (message: string) => { @@ -183,7 +217,7 @@ export const FiatPayForm = ({ return; } - if (!tokenAddress) { + if (!tokenSymbol) { onError('Please select a token.'); return; } @@ -223,16 +257,16 @@ export const FiatPayForm = ({ chainId, fortuneRequest, CURRENCY.usd, - fundAmount, - tokenAddress, + amount, + tokenSymbol, ); } else if (jobType === JobType.CVAT && cvatRequest) { await createCvatJob( chainId, cvatRequest, CURRENCY.usd, - fundAmount, - tokenAddress, + amount, + tokenSymbol, ); } else if (jobType === JobType.HCAPTCHA && hCaptchaRequest) { await createHCaptchaJob(chainId, hCaptchaRequest); @@ -294,7 +328,12 @@ export const FiatPayForm = ({ variant="outlined" value={amount} type="number" - onChange={(e) => setAmount(e.target.value)} + onChange={(e) => { + let value = e.target.value; + if (/^\d*\.?\d{0,2}$/.test(value)) { + setAmount(value); + } + }} sx={{ mb: 2 }} /> {selectedCard ? ( @@ -337,10 +376,8 @@ export const FiatPayForm = ({ )} - setTokenAddress(e.target.value as string) - } + value={tokenSymbol} + onTokenChange={handleTokenChange} /> @@ -369,39 +406,27 @@ export const FiatPayForm = ({ Account Balance {user?.balance && ( - ~ {user?.balance?.amount?.toFixed(2)} USD + {Number(currentBalance.toFixed(6))} USD )} - Fees - - ( - {Number(jobLauncherFee) >= 0 - ? `${Number(jobLauncherFee)}%` - : 'loading...'} - ) {feeAmount.toFixed(2)} USD - - - - Payment method - Balance + Amount - {balancePayAmount.toFixed(2)} USD + {amount + ? `${Number(Number(amount)?.toFixed(6))} USD` + : ''} + Fee - Credit Card + ( + {Number(jobLauncherFee) >= 0 + ? `${Number(jobLauncherFee)}%` + : 'loading...'} + ){' '} + {amount && feeAmount + ? `${Number(feeAmount?.toFixed(6))} USD` + : ''} + + + + Total payment + + {amount && totalAmount + ? `${Number(totalAmount?.toFixed(6))} USD` + : ''} + + + + + Payment method + + + Balance - {creditCardPayAmount.toFixed(2)} USD + {amount + ? `${Number(balancePayAmount?.toFixed(6))} USD` + : ''} - Total - {totalAmount.toFixed(2)} USD + + Credit Card + + + {amount && creditCardPayAmount + ? `${Number(creditCardPayAmount?.toFixed(6))} USD` + : ''} + + + + Fund Amount + + {tokenSymbol && fundAmount + ? `${Number(fundAmount?.toFixed(6))} ${tokenSymbol?.toUpperCase()}` + : ''} + + @@ -449,7 +530,7 @@ export const FiatPayForm = ({ !amount || (!payWithAccountBalance && !selectedCard) || hasError || - !tokenAddress + !tokenSymbol } > Pay now diff --git a/packages/apps/job-launcher/client/src/components/Jobs/Create/FundingMethod.tsx b/packages/apps/job-launcher/client/src/components/Jobs/Create/FundingMethod.tsx index d63c25427f..4d7e12f2fa 100644 --- a/packages/apps/job-launcher/client/src/components/Jobs/Create/FundingMethod.tsx +++ b/packages/apps/job-launcher/client/src/components/Jobs/Create/FundingMethod.tsx @@ -4,7 +4,6 @@ import { useNavigate } from 'react-router-dom'; import { useAccount } from 'wagmi'; import fundCryptoImg from '../../../assets/fund-crypto.png'; import fundFiatImg from '../../../assets/fund-fiat.png'; -import { IS_MAINNET } from '../../../constants/chains'; import { useCreateJobPageUI } from '../../../providers/CreateJobPageUIProvider'; import { useAppSelector } from '../../../state'; import { PayMethod } from '../../../types'; @@ -47,7 +46,7 @@ export const FundingMethod = () => { > {user?.whitelisted && ( - + { )} - {!IS_MAINNET && ( - + + + fiat + + Click to pay with credit card + - fiat - - Click to pay with credit card - - { + changePayMethod?.(PayMethod.Fiat); + goToNextStep?.(); }} > - - - + Pay with Credit Card + + - - )} + + `${fundAmount} HMT`, + render: ({ fundAmount, currency }) => + `${Number(fundAmount)} ${currency.toUpperCase()}`, }, { id: 'status', label: 'Status' }, { diff --git a/packages/apps/job-launcher/client/src/components/TokenSelect/index.tsx b/packages/apps/job-launcher/client/src/components/TokenSelect/index.tsx index 80bfa2bfb6..c6ae0cd3b4 100644 --- a/packages/apps/job-launcher/client/src/components/TokenSelect/index.tsx +++ b/packages/apps/job-launcher/client/src/components/TokenSelect/index.tsx @@ -7,32 +7,41 @@ import { Select, SelectProps, } from '@mui/material'; -import { FC, useMemo } from 'react'; +import { FC, useEffect, useState } from 'react'; import { TOKEN_ICONS } from '../../components/Icons/chains'; -import { SUPPORTED_TOKEN_SYMBOLS } from '../../constants'; -import { NETWORK_TOKENS } from '../../constants/chains'; +import * as paymentService from '../../services/payment'; type TokenSelectProps = SelectProps & { chainId: ChainId; + onTokenChange: (symbol: string, address: string, decimals: number) => void; }; export const TokenSelect: FC = (props) => { - const availableTokens = useMemo(() => { - return SUPPORTED_TOKEN_SYMBOLS.filter( - (symbol) => - NETWORK_TOKENS[props.chainId as keyof typeof NETWORK_TOKENS]?.[ - symbol.toLowerCase() - ], - ); + const [availableTokens, setAvailableTokens] = useState<{ + [key: string]: { + address: string; + decimals: number; + }; + }>({}); + + useEffect(() => { + const fetchTokensData = async () => { + const tokens = await paymentService.getTokensAvailable(props.chainId); + setAvailableTokens(tokens); + }; + + fetchTokensData(); }, [props.chainId]); return ( - Funding token + + {props.label ?? 'Funding token'} +