diff --git a/packages/apps/human-app/frontend/package.json b/packages/apps/human-app/frontend/package.json index 8180ab4879..b8912e8602 100644 --- a/packages/apps/human-app/frontend/package.json +++ b/packages/apps/human-app/frontend/package.json @@ -20,6 +20,7 @@ "@emotion/styled": "^11.11.0", "@faker-js/faker": "^9.7.0", "@fontsource/inter": "^5.0.17", + "@fontsource/roboto": "^5.2.6", "@hcaptcha/react-hcaptcha": "^0.3.6", "@hookform/resolvers": "^5.0.1", "@human-protocol/sdk": "workspace:*", diff --git a/packages/apps/human-app/frontend/src/main.tsx b/packages/apps/human-app/frontend/src/main.tsx index a8fa0e6415..ccf792a2b6 100644 --- a/packages/apps/human-app/frontend/src/main.tsx +++ b/packages/apps/human-app/frontend/src/main.tsx @@ -12,6 +12,9 @@ import '@fontsource/inter/400.css'; import '@fontsource/inter/500.css'; import '@fontsource/inter/600.css'; import '@fontsource/inter/800.css'; +import '@fontsource/roboto'; +import '@fontsource/roboto/400.css'; +import '@fontsource/roboto/500.css'; import { WalletConnectProvider } from '@/shared/contexts/wallet-connect'; import { Web3AuthProvider } from '@/modules/auth-web3/context/web3-auth-context'; import { JWTExpirationCheck } from '@/shared/contexts/jwt-expiration-check'; diff --git a/packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx b/packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx index 19373ee981..02617421d2 100644 --- a/packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx +++ b/packages/apps/human-app/frontend/src/modules/governance-banner/components/governance-banner.tsx @@ -1,10 +1,9 @@ import AccessTimeIcon from '@mui/icons-material/AccessTime'; -import { Grid, Link as MuiLink, Typography } from '@mui/material'; +import { Box, Grid, Link as MuiLink, Typography } from '@mui/material'; import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { env } from '@/shared/env'; import { useColorMode } from '@/shared/contexts/color-mode'; -import { useIsMobile } from '@/shared/hooks/use-is-mobile'; import { useWorkerIdentityVerificationStatus } from '@/modules/worker/profile/hooks'; import { useActiveProposalQuery } from '../hooks/use-active-proposal-query'; @@ -13,8 +12,8 @@ export function GovernanceBanner() { const { data, isLoading, isError } = useActiveProposalQuery(); const { isVerificationCompleted } = useWorkerIdentityVerificationStatus(); const { colorPalette } = useColorMode(); + const { text, background } = colorPalette.banner; const [timeRemaining, setTimeRemaining] = useState('00:00:00'); - const isMobile = useIsMobile('lg'); useEffect(() => { if (!data?.deadline) return; @@ -55,52 +54,41 @@ export function GovernanceBanner() { return ( {/* Left side: Countdown & "X votes" */} - - - {t('governance.timeToReveal', 'Time to reveal vote')}: - - - - {timeRemaining} - + + + + {t('governance.timeToReveal', 'Time to reveal vote')}: + + + {timeRemaining} + + {totalVotes} {t('governance.votes', 'votes')} @@ -111,22 +99,16 @@ export function GovernanceBanner() { item xs={12} sm - sx={{ - display: 'flex', - justifyContent: isMobile ? 'flex-start' : 'flex-end', - mt: isMobile ? 2 : 0, - mr: isMobile ? 0 : 2, - }} + display="flex" + justifyContent={{ xs: 'flex-start', sm: 'flex-end' }} > {t('governance.moreDetails', 'More details')} → diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-report-abuse.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-report-abuse.ts new file mode 100644 index 0000000000..5bb6d38a91 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/hooks/use-report-abuse.ts @@ -0,0 +1,22 @@ +import { useMutation } from '@tanstack/react-query'; +import { ApiClientError } from '@/api/http-api-client'; +import * as jobsService from '../../services/jobs.service'; +import type { ReportAbuseBody } from '../../types'; + +interface ReportAbuseMutationOptions { + onError?: (status: number) => void; +} + +export function useReportAbuseMutation({ + onError, +}: ReportAbuseMutationOptions) { + return useMutation({ + mutationFn: (data: ReportAbuseBody) => jobsService.reportAbuse(data), + mutationKey: ['reportAbuse'], + onError: (error) => { + if (error instanceof ApiClientError) { + onError?.(error.status); + } + }, + }); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/index.ts index 9dee5f52de..ca383549de 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/index.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/index.ts @@ -2,6 +2,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'; +export * from './more-button'; +export * from './report-abuse-modal'; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/more-button.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/more-button.tsx new file mode 100644 index 0000000000..492459017f --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/more-button.tsx @@ -0,0 +1,103 @@ +/* eslint-disable camelcase -- ...*/ +import { useState } from 'react'; +import MoreHorizIcon from '@mui/icons-material/MoreHoriz'; +import { Button, MenuList, ListItemButton, Popover } from '@mui/material'; +import { useParams } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; +import { useModal } from '@/shared/contexts/modal-context'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { useResignJobMutation } from '../my-jobs/hooks'; +import { type MyJob } from '../schemas'; +import { ReportAbuseModal } from './report-abuse-modal'; + +interface MoreButtonProps { + job: MyJob; + isDisabled: boolean; +} + +export function MoreButton({ job, isDisabled }: MoreButtonProps) { + const [anchorEl, setAnchorEl] = useState(null); + const { address: oracleAddress } = useParams<{ address: string }>(); + const { mutate: rejectTaskMutation } = useResignJobMutation(); + const { openModal, closeModal } = useModal(); + const isMobile = useIsMobile(); + const { t } = useTranslation(); + + const isOpen = Boolean(anchorEl); + + const handleCancelTask = () => { + setAnchorEl(null); + rejectTaskMutation({ + oracle_address: oracleAddress ?? '', + assignment_id: job.assignment_id, + }); + }; + + const handleOpenReportAbuseModal = () => { + setAnchorEl(null); + openModal({ + content: ( + + ), + showCloseButton: false, + }); + }; + + return ( + <> + + { + setAnchorEl(null); + }} + anchorOrigin={{ + vertical: isMobile ? 'top' : 'bottom', + horizontal: 'right', + }} + transformOrigin={{ + vertical: isMobile ? 'bottom' : 'top', + horizontal: 'right', + }} + slotProps={{ + paper: { + elevation: 8, + sx: { + mt: isMobile ? -1 : 1, + }, + }, + }} + > + + + {t('worker.reportAbuse.cancel')} + + + {t('worker.reportAbuse.reportAbuse')} + + + + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/my-jobs-table-actions.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/my-jobs-table-actions.tsx index 5ad4849298..886dc72875 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/my-jobs-table-actions.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/my-jobs-table-actions.tsx @@ -1,11 +1,9 @@ -/* eslint-disable camelcase -- ...*/ -import { Link, useParams } from 'react-router-dom'; +import { Link } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { TableButton } from '@/shared/components/ui/table-button'; -import { useResignJobMutation } from '../my-jobs/hooks'; import { MyJobStatus } from '../types'; import { type MyJob } from '../schemas'; -import { RejectButton } from './reject-button'; +import { MoreButton } from './more-button'; interface MyJobsTableRejectActionProps { job: MyJob; @@ -15,10 +13,7 @@ export function MyJobsTableActions({ job, }: Readonly) { const { t } = useTranslation(); - const { mutate: rejectTaskMutation, isPending: isRejectPending } = - useResignJobMutation(); - const { address: oracleAddress } = useParams<{ address: string }>(); - const buttonDisabled = job.status !== MyJobStatus.ACTIVE || isRejectPending; + const isDisabled = job.status !== MyJobStatus.ACTIVE; if (!job.url) { return null; @@ -28,24 +23,19 @@ export function MyJobsTableActions({ <> {t('worker.jobs.solve')} - { - rejectTaskMutation({ - oracle_address: oracleAddress ?? '', - assignment_id: job.assignment_id, - }); - }} - /> + ); } diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/reject-button.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/reject-button.tsx deleted file mode 100644 index d3f78d05ea..0000000000 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/reject-button.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import CloseIcon from '@mui/icons-material/Close'; -import type { CustomButtonProps } from '@/shared/components/ui/button'; -import { TableButton } from '@/shared/components/ui/table-button'; - -export function RejectButton(props: CustomButtonProps) { - return ( - - - - ); -} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/report-abuse-modal.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/report-abuse-modal.tsx new file mode 100644 index 0000000000..879ebd2c65 --- /dev/null +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/report-abuse-modal.tsx @@ -0,0 +1,169 @@ +/* eslint-disable camelcase */ +import { useState } from 'react'; +import { + Box, + Button, + CircularProgress, + Stack, + Typography, + TextField, +} from '@mui/material'; +import ErrorIcon from '@mui/icons-material/Error'; +import SuccessIcon from '@mui/icons-material/CheckCircle'; +import { useTranslation } from 'react-i18next'; +import { useIsMobile } from '@/shared/hooks/use-is-mobile'; +import { colorPalette } from '@/shared/styles/color-palette'; +import { useReportAbuseMutation } from '../available-jobs/hooks/use-report-abuse'; + +interface ReportAbuseModalProps { + escrowAddress: string; + chainId: number; + close: () => void; +} + +const ABUSE_ERROR = 'Abuse has already been reported'; + +function ErrorState({ error }: { error: string }) { + const { t } = useTranslation(); + + const isAbuseError = error === ABUSE_ERROR; + const errorColor = colorPalette.error.main; + + return ( + + + {isAbuseError ? ( + <> + + {t('worker.reportAbuse.modalHeaderAlreadyReportedError')} + + + {t('worker.reportAbuse.modalParagraphAlreadyReportedError')} + + + ) : ( + + {t('worker.reportAbuse.modalUnknownError')} + + )} + + ); +} + +function SuccessState() { + const { t } = useTranslation(); + return ( + + + + {t('worker.reportAbuse.modalSuccessHeader')} + + + {t('worker.reportAbuse.modalSuccessParagraph')} + + + ); +} + +export function ReportAbuseModal({ + escrowAddress, + chainId, + close, +}: ReportAbuseModalProps) { + const [reason, setReason] = useState(''); + const [error, setError] = useState(''); + const isMobile = useIsMobile(); + const { t } = useTranslation(); + + const { + mutate: reportAbuseMutation, + isSuccess, + isError, + isIdle, + isPending, + } = useReportAbuseMutation({ + onError: (status) => { + if (status === 422) { + setError(ABUSE_ERROR); + } else { + setError('Something went wrong'); + } + }, + }); + + const isIdleOrLoading = isIdle || isPending; + + const handleReportAbuse = () => { + reason.trim().length > 0 && + reportAbuseMutation({ + escrow_address: escrowAddress, + chain_id: chainId, + reason: reason.trim(), + }); + }; + + return ( + + + {t('worker.reportAbuse.modalHeader')} + + {isIdleOrLoading && ( + + {t('worker.reportAbuse.modalParagraph')} + + )} + {isPending && } + {isError && } + {isSuccess && } + { + setReason(e.target.value); + }} + /> + + + + + + ); +} diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/columns.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/columns.tsx index 4d7aa02284..2f6767d2f6 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/columns.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/columns.tsx @@ -135,7 +135,14 @@ export const getColumnsDefinition = ({ size: 100, enableSorting: true, Cell: (props) => ( - + ), diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-list-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-list-mobile.tsx index ea51b3ce32..02df479459 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-list-mobile.tsx +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-list-mobile.tsx @@ -181,7 +181,7 @@ export function MyJobsListMobile() { sx={{ display: 'flex', justifyContent: 'flex-end', - gap: '1rem', + gap: 1, width: '100%', }} > diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/services/jobs.service.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/services/jobs.service.ts index 8abcbb9c0c..166dea37f9 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/services/jobs.service.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/services/jobs.service.ts @@ -10,6 +10,7 @@ import { type RefreshJobsBody, type RejectTaskBody, type AvailableJobsSuccessResponse, + type ReportAbuseBody, } from '../types'; const apiPaths = { @@ -19,6 +20,7 @@ const apiPaths = { resignJob: '/assignment/resign-job', refreshJobs: '/assignment/refresh', uiConfig: '/ui-config', + reportAbuse: '/abuse/report', }; async function fetchAvailableJobs(args: JobsBody) { @@ -102,4 +104,17 @@ async function refreshJobs(data: RefreshJobsBody) { } } -export { fetchAvailableJobs, fetchMyJobs, assignJob, resignJob, refreshJobs }; +function reportAbuse(data: ReportAbuseBody) { + return authorizedHumanAppApiClient.post(apiPaths.reportAbuse, { + body: { ...data }, + }); +} + +export { + fetchAvailableJobs, + fetchMyJobs, + assignJob, + resignJob, + refreshJobs, + reportAbuse, +}; diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/types.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/types.ts index cb6f85f3c4..d2317b7cf0 100644 --- a/packages/apps/human-app/frontend/src/modules/worker/jobs/types.ts +++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/types.ts @@ -52,3 +52,9 @@ export interface AssignJobBody { export interface RefreshJobsBody { oracle_address: string; } + +export interface ReportAbuseBody { + escrow_address: string; + chain_id: number; + reason: string; +} diff --git a/packages/apps/human-app/frontend/src/router/components/layout/protected/bottom-menu-items-list.tsx b/packages/apps/human-app/frontend/src/router/components/layout/protected/bottom-menu-items-list.tsx index 347a53071e..8bf0bafb4d 100644 --- a/packages/apps/human-app/frontend/src/router/components/layout/protected/bottom-menu-items-list.tsx +++ b/packages/apps/human-app/frontend/src/router/components/layout/protected/bottom-menu-items-list.tsx @@ -13,7 +13,6 @@ import { colorPalette } from '@/shared/styles/color-palette'; import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette'; import { isDrawerItem } from '../helpers'; import { type MenuItem, type DrawerItem } from './drawer-navigation'; -import { NAVBAR_PADDING } from './navbar'; interface MenuItemListProps { handleItemClick: (item: DrawerItem) => void; @@ -37,7 +36,7 @@ export function BottomMenuItemsList({ direction="row" sx={{ width: '100%', - mx: isMobile ? '28px' : NAVBAR_PADDING, + mx: isMobile ? 7 : 2, }} > {item} diff --git a/packages/apps/human-app/frontend/src/router/components/layout/protected/navbar.tsx b/packages/apps/human-app/frontend/src/router/components/layout/protected/navbar.tsx index e8172f18e1..685c95bc61 100644 --- a/packages/apps/human-app/frontend/src/router/components/layout/protected/navbar.tsx +++ b/packages/apps/human-app/frontend/src/router/components/layout/protected/navbar.tsx @@ -9,8 +9,6 @@ import { useIsHCaptchaLabelingPage } from '@/shared/hooks/use-is-hcaptcha-labeli import { useColorMode } from '@/shared/contexts/color-mode'; import { useHandleMainNavIconClick } from '@/shared/hooks/use-handle-main-nav-icon-click'; -export const NAVBAR_PADDING = '16px'; - interface NavbarProps { open: boolean; setOpen: (open: boolean) => void; @@ -74,8 +72,8 @@ export function Navbar({ backgroundColor: colorPalette.backgroundColor, display: { xs: 'flex', md: 'none' }, width: '100%', - px: isMobile ? NAVBAR_PADDING : 0, - py: isMobile ? '32px' : 0, + px: isMobile ? 4 : 0, + py: isMobile ? 3 : 0, zIndex: '130', position: open ? 'sticky' : 'relative', top: open ? '0' : 'unset', diff --git a/packages/apps/human-app/frontend/src/router/components/layout/protected/top-menu-items-list.tsx b/packages/apps/human-app/frontend/src/router/components/layout/protected/top-menu-items-list.tsx index 529a3222f0..972ea1ee56 100644 --- a/packages/apps/human-app/frontend/src/router/components/layout/protected/top-menu-items-list.tsx +++ b/packages/apps/human-app/frontend/src/router/components/layout/protected/top-menu-items-list.tsx @@ -13,7 +13,6 @@ import { colorPalette } from '@/shared/styles/color-palette'; import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette'; import { isDrawerItem } from '../helpers'; import { type DrawerItem, type MenuItem } from './drawer-navigation'; -import { NAVBAR_PADDING } from './navbar'; interface MenuItemListProps { handleItemClick: (item: DrawerItem) => void; @@ -38,12 +37,7 @@ export function TopMenuItemsList({ if (!isDrawerItem(item)) { return ( - + {item} diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/modal/global-modal.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/modal/global-modal.tsx index 8061f5a401..16958d5dc7 100644 --- a/packages/apps/human-app/frontend/src/shared/components/ui/modal/global-modal.tsx +++ b/packages/apps/human-app/frontend/src/shared/components/ui/modal/global-modal.tsx @@ -12,6 +12,11 @@ export function GlobalModal() { open={open} onClose={closeModal} onTransitionExited={onTransitionExited} + PaperProps={{ + sx: { + flex: 1, + }, + }} >