{shortAddress}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/index.ts
index ca383549de..4cffc4caf3 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/index.ts
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/index.ts
@@ -1,8 +1,5 @@
export * from './evm-address';
-export * from './jobs-tab-panel';
export * from './my-jobs-table-actions';
-export * from './escrow-address-search-form';
export * from './reward-amount';
-export * from './sorting';
export * from './more-button';
-export * from './report-abuse-modal';
+export * from './report-abuse-dialog';
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/jobs-tab-panel.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/jobs-tab-panel.tsx
deleted file mode 100644
index b87990ffbf..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/jobs-tab-panel.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { Box } from '@mui/material';
-
-interface TabPanelProps {
- children?: React.ReactNode;
- index: number;
- activeTab: number;
-}
-
-export function TabPanel({ children, index, activeTab }: TabPanelProps) {
- return (
-
- {activeTab === index && {children}}
-
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/more-button.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/more-button.tsx
index a79920ed10..d8674df036 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/more-button.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/more-button.tsx
@@ -1,14 +1,15 @@
import { useState } from 'react';
import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
import { Button, MenuList, ListItemButton, Popover } from '@mui/material';
-import { useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
-import { useModal } from '@/shared/contexts/modal-context';
+
import { useIsMobile } from '@/shared/hooks/use-is-mobile';
import { TopNotificationType, useNotification } from '@/shared/hooks';
import { useResignJobMutation } from '../my-jobs/hooks';
import { type MyJob } from '../schemas';
-import { ReportAbuseModal } from './report-abuse-modal';
+import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode';
+import { useMyJobsFilterStore } from '../hooks';
+import { ReportAbuseDialog } from './report-abuse-dialog';
interface MoreButtonProps {
job: MyJob;
@@ -17,20 +18,23 @@ interface MoreButtonProps {
export function MoreButton({ job, isDisabled }: MoreButtonProps) {
const [anchorEl, setAnchorEl] = useState(null);
- const { address: oracleAddress } = useParams<{ address: string }>();
- const { mutateAsync: rejectTaskMutation } = useResignJobMutation();
- const { openModal, closeModal } = useModal();
- const isMobile = useIsMobile();
+ const [isDialogOpen, setIsDialogOpen] = useState(false);
+
+ const { colorPalette } = useColorMode();
const { t } = useTranslation();
+ const isMobile = useIsMobile();
const { showNotification } = useNotification();
+ const { filterParams } = useMyJobsFilterStore();
+ const { mutateAsync: rejectTaskMutation } = useResignJobMutation();
+
const isOpen = Boolean(anchorEl);
const handleCancelTask = async () => {
setAnchorEl(null);
try {
await rejectTaskMutation({
- oracle_address: oracleAddress ?? '',
+ oracle_address: filterParams.oracle_address ?? '',
assignment_id: job.assignment_id,
});
} catch {
@@ -44,16 +48,7 @@ export function MoreButton({ job, isDisabled }: MoreButtonProps) {
const handleOpenReportAbuseModal = () => {
setAnchorEl(null);
- openModal({
- content: (
-
- ),
- showCloseButton: false,
- });
+ setIsDialogOpen(true);
};
return (
@@ -62,12 +57,12 @@ export function MoreButton({ job, isDisabled }: MoreButtonProps) {
disabled={isDisabled}
sx={{
minWidth: 'unset',
- width: { xs: '48px', md: '30px' },
- height: { xs: '48px', md: '30px' },
+ width: { xs: '44px', md: '30px' },
+ height: { xs: '44px', md: '30px' },
p: 1,
- border: isMobile ? '1px solid #858ec6' : 'none',
+ border: { xs: `1px solid ${colorPalette.border.main}`, md: 'none' },
borderRadius: '4px',
- color: '#858ec6',
+ color: colorPalette.text.auxiliary100,
}}
onClick={(e) => {
if (!isDisabled) {
@@ -95,7 +90,7 @@ export function MoreButton({ job, isDisabled }: MoreButtonProps) {
paper: {
elevation: 8,
sx: {
- mt: isMobile ? -1 : 1,
+ mt: { xs: -1, md: 1 },
},
},
}}
@@ -109,6 +104,12 @@ export function MoreButton({ job, isDisabled }: MoreButtonProps) {
+ setIsDialogOpen(false)}
+ escrowAddress={job.escrow_address}
+ chainId={job.chain_id}
+ />
>
);
}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/my-jobs-table-actions.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/my-jobs-table-actions.tsx
index 886dc72875..7501dcb0b4 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/my-jobs-table-actions.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/my-jobs-table-actions.tsx
@@ -1,17 +1,16 @@
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
+
import { TableButton } from '@/shared/components/ui/table-button';
import { MyJobStatus } from '../types';
import { type MyJob } from '../schemas';
import { MoreButton } from './more-button';
-interface MyJobsTableRejectActionProps {
+type Props = {
job: MyJob;
-}
+};
-export function MyJobsTableActions({
- job,
-}: Readonly) {
+export function MyJobsTableActions({ job }: Props) {
const { t } = useTranslation();
const isDisabled = job.status !== MyJobStatus.ACTIVE;
@@ -28,7 +27,7 @@ export function MyJobsTableActions({
target="_blank"
to={job.url}
sx={{
- height: { xs: '48px', md: '30px' },
+ height: { xs: '44px', md: '30px' },
maxWidth: { xs: 'unset', sm: '160px' },
flex: { xs: 1, md: 'unset' },
}}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/report-abuse-modal.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/report-abuse-dialog.tsx
similarity index 51%
rename from packages/apps/human-app/frontend/src/modules/worker/jobs/components/report-abuse-modal.tsx
rename to packages/apps/human-app/frontend/src/modules/worker/jobs/components/report-abuse-dialog.tsx
index 5cbfa52622..9e2bcfd6f3 100644
--- 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-dialog.tsx
@@ -1,26 +1,29 @@
import { useState } from 'react';
import {
Box,
- Button,
- CircularProgress,
FormControl,
MenuItem,
- Select as MuiSelect,
+ Select,
Stack,
Typography,
} from '@mui/material';
import ErrorIcon from '@mui/icons-material/Error';
import SuccessIcon from '@mui/icons-material/CheckCircle';
import { useTranslation } from 'react-i18next';
-import { useIsMobile } from '@/shared/hooks/use-is-mobile';
-import { colorPalette } from '@/shared/styles/color-palette';
+
+import { ResponsiveOverlay } from '@/shared/components/ui/responsive-overlay';
import { useReportAbuseMutation } from '../available-jobs/hooks/use-report-abuse';
+import { useIsMobile } from '@/shared/hooks/use-is-mobile';
+import { Button } from '@/shared/components/ui/button';
+import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode';
+import { Loader } from '@/shared/components/ui/loader';
-interface ReportAbuseModalProps {
+type Props = {
+ open: boolean;
+ onClose: () => void;
escrowAddress: string;
chainId: number;
- close: () => void;
-}
+};
const ABUSE_ERROR = 'Abuse has already been reported';
@@ -40,12 +43,21 @@ const REASON_OPTIONS = [
function ErrorState({ error }: { error: string }) {
const { t } = useTranslation();
+ const { colorPalette } = useColorMode();
const isAbuseError = error === ABUSE_ERROR;
const errorColor = colorPalette.error.main;
return (
-
+
{isAbuseError ? (
<>
@@ -56,7 +68,12 @@ function ErrorState({ error }: { error: string }) {
>
{t('worker.reportAbuse.modalHeaderAlreadyReportedError')}
-
+
{t('worker.reportAbuse.modalParagraphAlreadyReportedError')}
>
@@ -71,30 +88,49 @@ function ErrorState({ error }: { error: string }) {
function SuccessState() {
const { t } = useTranslation();
+ const { colorPalette } = useColorMode();
+
return (
-
+
-
+
{t('worker.reportAbuse.modalSuccessHeader')}
-
+
{t('worker.reportAbuse.modalSuccessParagraph')}
);
}
-export function ReportAbuseModal({
+export function ReportAbuseDialog({
+ open,
+ onClose,
escrowAddress,
chainId,
- close,
-}: ReportAbuseModalProps) {
+}: Props) {
const [reason, setReason] = useState('');
const [error, setError] = useState('');
- const isMobile = useIsMobile();
+
const { t } = useTranslation();
+ const { colorPalette } = useColorMode();
+ const isMobile = useIsMobile();
const {
mutate: reportAbuseMutation,
@@ -127,68 +163,98 @@ export function ReportAbuseModal({
};
return (
-
-
- {t('worker.reportAbuse.modalHeader')}
-
- {isIdleOrLoading && (
- <>
-
- {t('worker.reportAbuse.modalParagraph')}
-
-
- {
- setReason(e.target.value);
- }}
+
+
+ {t('worker.reportAbuse.modalHeader')}
+
+ {isIdleOrLoading && (
+ <>
+
- {REASON_OPTIONS.map((value) => (
-
- ))}
-
-
- >
+ {t('worker.reportAbuse.modalParagraph')}
+
+
+
+
+ >
+ )}
+
+ {isPending && (
+
+
+
)}
- {isPending && }
{isError && }
{isSuccess && }
-
+
-
+
);
}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/reward-amount.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/reward-amount.tsx
index f4af594c6c..ddfde6c4fb 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/reward-amount.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/reward-amount.tsx
@@ -1,5 +1,6 @@
-import Tooltip from '@mui/material/Tooltip';
-import Typography from '@mui/material/Typography';
+import { Tooltip, Typography } from '@mui/material';
+
+import { useIsMobile } from '@/shared/hooks/use-is-mobile';
export function RewardAmount({
reward_amount,
@@ -10,30 +11,49 @@ export function RewardAmount({
reward_token?: string;
color?: string;
}) {
+ const isMobile = useIsMobile();
+ const variant = isMobile ? 'body2' : 'body1';
+
if (!(reward_amount !== undefined && reward_token)) {
return '';
}
+
const parsedReward = Number(reward_amount);
const isNumeric = Number.isFinite(parsedReward);
+
if (!isNumeric) {
return (
-
+
{`${reward_amount} ${reward_token}`}
);
}
+
const hasDecimals = parsedReward - Math.floor(parsedReward) !== 0;
if (hasDecimals) {
return (
-
+
{`${parsedReward.toFixed(2)} ${reward_token}`}
);
}
+
return (
-
+
{`${reward_amount} ${reward_token}`}
);
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/sorting.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/sorting.tsx
deleted file mode 100644
index 4126e5fc53..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/sorting.tsx
+++ /dev/null
@@ -1,102 +0,0 @@
-import { t } from 'i18next';
-import Typography from '@mui/material/Typography';
-import Box from '@mui/material/Box';
-import ListItemButton from '@mui/material/ListItemButton';
-import List from '@mui/material/List';
-import { Grid } from '@mui/material';
-import { SortArrow } from '@/shared/components/ui/icons';
-import { useColorMode } from '@/shared/contexts/color-mode';
-import { Button } from '@/shared/components/ui/button';
-
-interface SortingProps {
- fromHighestSelected: boolean;
- fromLowestSelected: boolean;
- sortFromHighest: () => void;
- sortFromLowest: () => void;
- clear: () => void;
- label: React.ReactElement;
-}
-
-export function Sorting({
- fromHighestSelected,
- fromLowestSelected,
- sortFromHighest,
- sortFromLowest,
- clear,
- label,
-}: SortingProps) {
- const { colorPalette } = useColorMode();
-
- return (
- <>
- {label}
-
-
-
-
- {t('worker.jobs.sortDirection.fromHighest')}
-
-
-
-
-
-
-
- From lowest
-
-
-
-
-
-
- >
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/index.ts
index b977266dbe..8cb316adfb 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/index.ts
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/index.ts
@@ -1,6 +1,4 @@
-export * from './use-get-all-networks';
export * from './use-jobs-notifications';
-export * from './use-job-types-oracles-table';
export * from './use-jobs-filter-store';
export * from './use-my-jobs-filter-store';
export * from './use-get-my-jobs-data';
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-all-networks.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-all-networks.ts
deleted file mode 100644
index 61daf258dd..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-all-networks.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { useMemo } from 'react';
-import { getEnabledChainsByUiConfig } from '@/modules/smart-contracts/chains';
-
-export const useGetAllNetworks = (chainIdsEnabled: number[]) => {
- const allNetworks = useMemo(() => {
- const chains = getEnabledChainsByUiConfig(chainIdsEnabled);
-
- return chains.map(({ chainId, name }) => ({
- option: chainId,
- name,
- }));
- }, [chainIdsEnabled]);
-
- return { allNetworks };
-};
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-my-jobs-data.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-my-jobs-data.ts
index 2576e22e10..d055d59693 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-my-jobs-data.ts
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-get-my-jobs-data.ts
@@ -1,48 +1,38 @@
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
-import { useParams } from 'react-router-dom';
+
import * as jobsService from '../services/jobs.service';
import { type MyJobPaginationResponse } from '../schemas';
-import {
- useMyJobsFilterStore,
- type MyJobsFilterStoreProps,
-} from './use-my-jobs-filter-store';
-
-type OracleParams = MyJobsFilterStoreProps['filterParams'] & {
- oracle_address: string;
-};
+import { useMyJobsFilterStore } from './use-my-jobs-filter-store';
export function useGetMyJobsData() {
const { filterParams } = useMyJobsFilterStore();
- const { address } = useParams<{ address: string }>();
- const queryParams: OracleParams = {
- ...filterParams,
- oracle_address: address ?? '',
- };
+ const queryParams = { ...filterParams };
return useQuery({
queryKey: ['fetchMyJobs', queryParams],
queryFn: async ({ signal }) =>
- jobsService.fetchMyJobs({ queryParams, signal }),
+ jobsService.fetchMyJobs({ queryParams: queryParams, signal }),
+ enabled: !!filterParams.oracle_address,
});
}
export function useInfiniteGetMyJobsData() {
const { filterParams } = useMyJobsFilterStore();
- const { address } = useParams<{ address: string }>();
- const queryParams: OracleParams = {
- ...filterParams,
- oracle_address: address ?? '',
- };
+ const { page: _page, ...queryParams } = filterParams;
return useInfiniteQuery({
initialPageParam: 0,
queryKey: ['myJobsInfinite', queryParams],
- queryFn: async ({ signal }) =>
- jobsService.fetchMyJobs({ queryParams, signal }),
+ queryFn: async ({ pageParam, signal }) =>
+ jobsService.fetchMyJobs({
+ queryParams: { ...queryParams, page: pageParam },
+ signal,
+ }),
+ enabled: !!filterParams.oracle_address,
getNextPageParam: (pageParams: MyJobPaginationResponse) => {
return pageParams.total_pages - 1 <= pageParams.page
? undefined
- : pageParams.page;
+ : pageParams.page + 1;
},
});
}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-job-types-oracles-table.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-job-types-oracles-table.tsx
deleted file mode 100644
index 7dadc09e9f..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-job-types-oracles-table.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { create } from 'zustand';
-
-export interface JobsTypesOraclesFilterStore {
- selectedJobTypes: string[];
- selectJobType: (jobType: string[]) => void;
-}
-
-export const useJobsTypesOraclesFilterStore =
- create((set) => ({
- selectedJobTypes: [],
- selectJobType: (jobTypes: string[]) => {
- set((state) => ({
- ...state,
- selectedJobTypes: jobTypes,
- }));
- },
- }));
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-notifications.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-notifications.tsx
index 474a8294f8..97322d8d96 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-notifications.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-jobs-notifications.tsx
@@ -10,7 +10,7 @@ export const useJobsNotifications = () => {
const onJobAssignmentSuccess = () => {
showNotification({
- message: t('worker.jobs.successFullyAssignedJob'),
+ message: t('worker.jobs.successfullyAssignedJob'),
type: TopNotificationType.SUCCESS,
durationMs: 5000,
});
@@ -19,7 +19,7 @@ export const useJobsNotifications = () => {
const onJobAssignmentError = (error: Error) => {
showNotification({
message: getErrorMessageForError(error),
- type: TopNotificationType.WARNING,
+ type: TopNotificationType.ERROR,
durationMs: 5000,
});
};
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-my-jobs-filter-store.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-my-jobs-filter-store.tsx
index 8138fd4027..4547d82a14 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-my-jobs-filter-store.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/hooks/use-my-jobs-filter-store.tsx
@@ -1,40 +1,32 @@
import { create } from 'zustand';
import type { PageSize } from '@/shared/types/entity.type';
-import { SortDirection, SortField, type MyJobStatus } from '../types';
+import { SortDirection, SortField, type StatusFilterType } from '../types';
export interface MyJobsFilterStoreProps {
filterParams: {
- sort?: SortDirection;
- sort_field?: SortField;
- job_type?: string;
- status?: MyJobStatus;
- escrow_address?: string;
+ status?: StatusFilterType;
+ oracle_address?: string;
page: number;
page_size: PageSize;
- chain_id?: number;
};
- availableJobTypes: string[];
setFilterParams: (
partialParams: Partial
) => void;
resetFilterParams: () => void;
- setSearchEscrowAddress: (escrow_address: string) => void;
- setOracleAddress: (oracleAddress: string) => void;
- setAvailableJobTypes: (jobTypes: string[]) => void;
setPageParams: (pageIndex: number, pageSize: PageSize) => void;
}
const initialFiltersState = {
- escrow_address: '',
+ oracle_address: undefined,
+ status: '',
page: 0,
page_size: 5,
sort_field: SortField.CREATED_AT,
- sort: SortDirection.DESC,
+ sort_direction: SortDirection.DESC,
} as const;
export const useMyJobsFilterStore = create((set) => ({
filterParams: initialFiltersState,
- availableJobTypes: [],
setFilterParams: (
partialParams: Partial
) => {
@@ -48,40 +40,25 @@ export const useMyJobsFilterStore = create((set) => ({
}));
},
setPageParams: (pageIndex: number, pageSize: PageSize) => {
- set((state) => ({
- ...state,
- filterParams: {
- ...state.filterParams,
- page: pageIndex,
- page_size: pageSize,
- },
- }));
+ set((state) => {
+ if (
+ state.filterParams.page === pageIndex &&
+ state.filterParams.page_size === pageSize
+ ) {
+ return state;
+ }
+
+ return {
+ ...state,
+ filterParams: {
+ ...state.filterParams,
+ page: pageIndex,
+ page_size: pageSize,
+ },
+ };
+ });
},
resetFilterParams: () => {
set({ filterParams: initialFiltersState });
},
- setSearchEscrowAddress: (escrow_address: string) => {
- set((state) => ({
- ...state,
- filterParams: {
- ...state.filterParams,
- escrow_address,
- },
- }));
- },
- setOracleAddress: (oracleAddress: string) => {
- set((state) => ({
- ...state,
- filterParams: {
- ...state.filterParams,
- address: oracleAddress,
- },
- }));
- },
- setAvailableJobTypes: (jobTypes: string[]) => {
- set((state) => ({
- ...state,
- jobTypes,
- }));
- },
}));
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/jobs.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/jobs.page.tsx
index a4e79bd62c..0c4f80e810 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/jobs.page.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/jobs.page.tsx
@@ -2,17 +2,11 @@ import React, { useEffect, useState } from 'react';
import { Box, Grid, Paper, Stack, Tab, Tabs, Typography } from '@mui/material';
import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom';
-import { TableQueryContextProvider } from '@/shared/components/ui/table/table-query-context';
import { useIsMobile } from '@/shared/hooks/use-is-mobile';
import { useColorMode } from '@/shared/contexts/color-mode';
-import { NoRecords } from '@/shared/components/ui/no-records';
import { PageCardLoader } from '@/shared/components/ui/page-card';
-import { useGetUiConfig } from '@/shared/hooks';
import { useGetOracles } from '../hooks';
import { useGetOraclesNotifications } from '../hooks/use-get-oracles-notifications';
-import { TabPanel } from './components';
-import { AvailableJobsView } from './available-jobs';
-import { MyJobsView } from './my-jobs/my-jobs-view';
function generateTabA11yProps(index: number) {
return {
@@ -30,19 +24,13 @@ export function JobsPage() {
error,
} = useGetOracles();
- const {
- data: uiConfigData,
- isPending: isPendingUiConfig,
- isError: isErrorUiConfig,
- } = useGetUiConfig();
-
const { address: oracle_address } = useParams<{ address: string }>();
const { t } = useTranslation();
const [activeTab, setActiveTab] = useState(0);
const isMobile = useIsMobile();
- const isError = isErrorGetOracles || isErrorUiConfig;
- const isPending = isPendingGetOracles || isPendingUiConfig;
+ const isError = isErrorGetOracles;
+ const isPending = isPendingGetOracles;
const { onError } = useGetOraclesNotifications();
const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => {
@@ -91,49 +79,29 @@ export function JobsPage() {
)}
-
-
-
+
+
-
-
-
-
-
-
- {isError ? (
-
- ) : (
-
- )}
-
-
- {isError ? (
-
- ) : (
-
- )}
-
+
+
+
-
+
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/columns.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/columns.tsx
deleted file mode 100644
index 5e4594d32d..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/columns.tsx
+++ /dev/null
@@ -1,175 +0,0 @@
-import { t } from 'i18next';
-import Grid from '@mui/material/Grid';
-import { type MRT_ColumnDef } from 'material-react-table';
-import RefreshIcon from '@mui/icons-material/Refresh';
-import { TableHeaderCell } from '@/shared/components/ui/table/table-header-cell';
-import { getNetworkName } from '@/modules/smart-contracts/get-network-name';
-import { Button } from '@/shared/components/ui/button';
-import { Chip } from '@/shared/components/ui/chip';
-import type { JobType } from '@/modules/smart-contracts/EthKVStore/config';
-import { formatDate } from '@/shared/helpers/date';
-import {
- EvmAddress,
- RewardAmount,
- MyJobsTableActions,
-} from '../../../components';
-import { type MyJob } from '../../../schemas';
-import { StatusChip } from './status-chip';
-import { MyJobsExpiresAtSort } from './my-jobs-expires-at-sort';
-import { MyJobsJobTypeFilter } from './my-jobs-job-type-filter';
-import { MyJobsNetworkFilter } from './my-jobs-network-filter';
-import { MyJobsRewardAmountSort } from './my-jobs-reward-amount-sort';
-import { MyJobsStatusFilter } from './my-jobs-status-filter';
-
-export const getColumnsDefinition = ({
- refreshData,
- isRefreshTasksPending,
- chainIdsEnabled,
-}: {
- refreshData: () => void;
- isRefreshTasksPending: boolean;
- chainIdsEnabled: number[];
-}): MRT_ColumnDef[] => [
- {
- accessorKey: 'escrow_address',
- header: t('worker.jobs.escrowAddress'),
- size: 100,
- enableSorting: true,
- Cell: (props) => {
- return ;
- },
- },
- {
- accessorKey: 'network',
- header: t('worker.jobs.network'),
- size: 100,
- Cell: (props) => {
- return getNetworkName(props.row.original.chain_id);
- },
- Header: (
-
- }
- />
- ),
- },
- {
- accessorKey: 'reward_amount',
- header: t('worker.jobs.rewardAmount'),
- size: 100,
- enableSorting: true,
- Cell: (props) => {
- const { reward_amount, reward_token } = props.row.original;
- return (
-
- );
- },
- Header: (
- }
- />
- ),
- },
- {
- accessorKey: 'job_type',
- header: t('worker.jobs.jobType'),
- size: 100,
- enableSorting: true,
- Cell: ({ row }) => {
- const label = t(`jobTypeLabels.${row.original.job_type as JobType}`);
- return ;
- },
- Header: (
- }
- />
- ),
- },
- {
- accessorKey: 'expires_at',
- header: t('worker.jobs.expiresAt'),
- size: 100,
- enableSorting: true,
- Cell: (props) => {
- return formatDate(props.row.original.expires_at);
- },
- Header: (
- }
- />
- ),
- },
- {
- accessorKey: 'status',
- header: t('worker.jobs.status'),
- size: 100,
- enableSorting: true,
- Cell: (props) => {
- const status = props.row.original.status;
- return ;
- },
- Header: (
- }
- />
- ),
- },
- {
- accessorKey: 'assignment_id',
- header: t('worker.jobs.refresh'),
- size: 100,
- enableSorting: true,
- Cell: (props) => (
-
-
-
- ),
- Header: (
-
-
-
- ),
- },
-];
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/index.ts
index 677305df30..ed7a437325 100644
--- 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
@@ -1 +1,3 @@
export * from './my-jobs-table';
+export * from './my-jobs-filters';
+export * from './status-filter';
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-expires-at-sort.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-expires-at-sort.tsx
deleted file mode 100644
index 608edc3452..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-expires-at-sort.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { t } from 'i18next';
-import { Sorting } from '@/shared/components/ui/table/table-header-menu/sorting';
-import { useMyJobsFilterStore } from '../../../hooks';
-import { SortDirection, SortField } from '../../../types';
-
-export function MyJobsExpiresAtSort() {
- const { setFilterParams } = useMyJobsFilterStore();
-
- const sortAscExpiresAt = () => {
- setFilterParams({
- sort_field: SortField.EXPIRES_AT,
- sort: SortDirection.ASC,
- });
- };
-
- const sortDescExpiresAt = () => {
- setFilterParams({
- sort_field: SortField.EXPIRES_AT,
- sort: SortDirection.DESC,
- });
- };
-
- return (
- {
- setFilterParams({
- sort_field: undefined,
- sort: undefined,
- });
- }}
- sortingOptions={[
- {
- label: t('worker.jobs.sortDirection.closestToNow'),
- sortCallback: sortAscExpiresAt,
- },
- {
- label: t('worker.jobs.sortDirection.furthestToNow'),
- sortCallback: sortDescExpiresAt,
- },
- ]}
- />
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-filters.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-filters.tsx
new file mode 100644
index 0000000000..c957fed112
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-filters.tsx
@@ -0,0 +1,197 @@
+import { useEffect, useState, type SubmitEvent } from 'react';
+import { useTranslation } from 'react-i18next';
+import {
+ FormControlLabel,
+ IconButton,
+ Radio,
+ RadioGroup,
+ Stack,
+ Typography,
+} from '@mui/material';
+
+import { FilterIcon } from '@/shared/components/ui/icons';
+import { useColorMode } from '@/shared/contexts/color-mode';
+import { ResponsiveOverlay } from '@/shared/components/ui/responsive-overlay';
+import { useGetOracles } from '@/modules/worker/hooks/use-get-oracles';
+import { Alert } from '@/shared/components/ui/alert';
+import { Button } from '@/shared/components/ui/button';
+import { useIsMobile } from '@/shared/hooks/use-is-mobile';
+import { useMyJobsFilterStore } from '../../../hooks';
+import { type StatusFilterType } from '../../../types';
+import { STATUS_FILTER_OPTIONS } from './status-filter';
+
+const CVAT_ORACLE_NAME = 'cvat';
+
+const getDefaultOracleAddress = (
+ oracles: { address: string; name: string }[] | undefined
+) => {
+ if (!oracles?.length) {
+ return undefined;
+ }
+
+ return (
+ oracles.find((oracle) =>
+ oracle.name.toLowerCase().includes(CVAT_ORACLE_NAME)
+ )?.address ?? oracles[0].address
+ );
+};
+
+export function MyJobsFilters() {
+ const [isOpen, setIsOpen] = useState(false);
+ const [draftOracleAddress, setDraftOracleAddress] = useState('');
+ const [draftStatus, setDraftStatus] = useState('');
+
+ const { colorPalette } = useColorMode();
+ const { t } = useTranslation();
+ const isMobile = useIsMobile();
+ const { filterParams, setFilterParams } = useMyJobsFilterStore();
+
+ const { data: oraclesData, isError, isPending } = useGetOracles();
+
+ const defaultOracleAddress = getDefaultOracleAddress(oraclesData);
+ const selectedOracleAddress =
+ filterParams.oracle_address ?? defaultOracleAddress ?? '';
+ const selectedStatus = filterParams.status ?? '';
+
+ useEffect(() => {
+ if (!filterParams.oracle_address && defaultOracleAddress) {
+ setFilterParams({ oracle_address: defaultOracleAddress });
+ }
+ }, [defaultOracleAddress, filterParams.oracle_address, setFilterParams]);
+
+ useEffect(() => {
+ if (isOpen) {
+ setDraftOracleAddress(selectedOracleAddress);
+ setDraftStatus(selectedStatus);
+ }
+ }, [isOpen, selectedOracleAddress, selectedStatus]);
+
+ const handleSubmit = (e: SubmitEvent) => {
+ e.preventDefault();
+ setFilterParams({
+ oracle_address: draftOracleAddress,
+ status: draftStatus,
+ });
+ setIsOpen(false);
+ };
+
+ return (
+ <>
+ setIsOpen(!isOpen)}
+ >
+
+
+ setIsOpen(false)}
+ desktopSx={{ p: 0 }}
+ mobileSx={{ p: 0 }}
+ >
+
+
+
+ {t('worker.jobs.jobsFilter')}
+
+
+
+ {t('worker.jobs.oracles')}
+
+ {isError && (
+
+ {t('worker.oraclesTable.error.gettingOracles')}
+
+ )}
+ {
+ setDraftOracleAddress(oracleAddress);
+ }}
+ >
+ {(oraclesData ?? []).map((oracle) => (
+ }
+ label={
+
+ {oracle.name}
+
+ }
+ value={oracle.address}
+ />
+ ))}
+
+
+ {isMobile && (
+
+
+ {t('worker.jobs.status')}
+
+ {
+ setDraftStatus(status as StatusFilterType);
+ }}
+ >
+ {STATUS_FILTER_OPTIONS.map((option) => (
+ }
+ label={
+
+ {t(option.labelKey)}
+
+ }
+ value={option.value}
+ />
+ ))}
+
+
+ )}
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-job-type-filter.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-job-type-filter.tsx
deleted file mode 100644
index 7b9fd2e428..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-job-type-filter.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { useTranslation } from 'react-i18next';
-import { useMemo } from 'react';
-import { Filtering } from '@/shared/components/ui/table/table-header-menu/filtering';
-import { JOB_TYPES } from '@/shared/consts';
-import { useMyJobsFilterStore } from '../../../hooks';
-
-export function MyJobsJobTypeFilter() {
- const { t } = useTranslation();
- const { setFilterParams, filterParams } = useMyJobsFilterStore();
- const filteringOptions = useMemo(
- () =>
- JOB_TYPES.map((jobType) => ({
- name: t(`jobTypeLabels.${jobType}`),
- option: jobType,
- })),
- [t]
- );
- return (
- {
- setFilterParams({
- job_type: undefined,
- });
- }}
- filteringOptions={filteringOptions}
- isChecked={(option) => option === filterParams.job_type}
- setFiltering={(jobType) => {
- setFilterParams({
- job_type: jobType,
- });
- }}
- showClearButton
- showTitle
- />
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-network-filter.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-network-filter.tsx
deleted file mode 100644
index b404ffada7..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-network-filter.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Filtering } from '@/shared/components/ui/table/table-header-menu/filtering';
-import { useMyJobsFilterStore, useGetAllNetworks } from '../../../hooks';
-
-interface MyJobsNetworkFilterProps {
- chainIdsEnabled: number[];
-}
-
-export function MyJobsNetworkFilter({
- chainIdsEnabled,
-}: Readonly) {
- const { setFilterParams, filterParams } = useMyJobsFilterStore();
- const { allNetworks } = useGetAllNetworks(chainIdsEnabled);
-
- return (
- {
- setFilterParams({
- chain_id: undefined,
- });
- }}
- filteringOptions={allNetworks}
- isChecked={(option) => option === filterParams.chain_id}
- setFiltering={(chainId) => {
- setFilterParams({
- chain_id: chainId,
- });
- }}
- showClearButton
- showTitle
- />
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-reward-amount-sort.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-reward-amount-sort.tsx
deleted file mode 100644
index 2e1a335540..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-reward-amount-sort.tsx
+++ /dev/null
@@ -1,43 +0,0 @@
-import { t } from 'i18next';
-import { Sorting } from '@/shared/components/ui/table/table-header-menu/sorting';
-import { useMyJobsFilterStore } from '../../../hooks';
-import { SortDirection, SortField } from '../../../types';
-
-export function MyJobsRewardAmountSort() {
- const { setFilterParams } = useMyJobsFilterStore();
-
- const sortAscRewardAmount = () => {
- setFilterParams({
- sort_field: SortField.REWARD_AMOUNT,
- sort: SortDirection.ASC,
- });
- };
-
- const sortDescRewardAmount = () => {
- setFilterParams({
- sort_field: SortField.REWARD_AMOUNT,
- sort: SortDirection.DESC,
- });
- };
-
- return (
- {
- setFilterParams({
- sort_field: undefined,
- sort: undefined,
- });
- }}
- sortingOptions={[
- {
- label: t('worker.jobs.sortDirection.fromHighest'),
- sortCallback: sortDescRewardAmount,
- },
- {
- label: t('worker.jobs.sortDirection.fromLowest'),
- sortCallback: sortAscRewardAmount,
- },
- ]}
- />
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-status-filter.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-status-filter.tsx
deleted file mode 100644
index 4c17f3f157..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-status-filter.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import capitalize from 'lodash/capitalize';
-import { Filtering } from '@/shared/components/ui/table/table-header-menu/filtering';
-import { useMyJobsFilterStore } from '../../../hooks';
-import { MyJobStatus } from '../../../types';
-
-export function MyJobsStatusFilter() {
- const { setFilterParams, filterParams } = useMyJobsFilterStore();
-
- return (
- {
- setFilterParams({
- status: undefined,
- });
- }}
- filteringOptions={Object.values(MyJobStatus).map((status) => ({
- name: capitalize(status),
- option: status,
- }))}
- isChecked={(status) => status === filterParams.status}
- setFiltering={(status) => {
- setFilterParams({
- status,
- });
- }}
- showClearButton
- showTitle
- />
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-table.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-table.tsx
index 0fd5c61c70..2eb57540a0 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-table.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/my-jobs-table.tsx
@@ -1,126 +1,141 @@
-import { t } from 'i18next';
-import { useCallback, useEffect, useMemo, useState } from 'react';
-import { useParams } from 'react-router-dom';
+import { useEffect, useMemo, useState } from 'react';
import {
MaterialReactTable,
+ type MRT_PaginationState,
useMaterialReactTable,
} from 'material-react-table';
+
import { useColorMode } from '@/shared/contexts/color-mode';
import { createTableDarkMode } from '@/shared/styles/create-table-dark-mode';
-import { EscrowAddressSearchForm } from '../../../components';
import { useGetMyJobsData, useMyJobsFilterStore } from '../../../hooks';
-import { useRefreshJobsMutation } from '../../hooks';
-import { getColumnsDefinition } from './columns';
-
-interface MyJobsTableProps {
- chainIdsEnabled: number[];
-}
+import { useGetMyJobsColumns } from '../../hooks/use-get-my-jobs-columns';
-export function MyJobsTable({ chainIdsEnabled }: Readonly) {
+export function MyJobsTable() {
const { colorPalette, isDarkMode } = useColorMode();
- const {
- setSearchEscrowAddress,
- setPageParams,
- filterParams,
- resetFilterParams,
- } = useMyJobsFilterStore();
- const { data: tableData, status: tableStatus } = useGetMyJobsData();
+ const { data: tableData, isPending, isError } = useGetMyJobsData();
+ const { setPageParams, filterParams } = useMyJobsFilterStore();
+ const [paginationState, setPaginationState] = useState(
+ () => ({
+ pageIndex: filterParams.page,
+ pageSize: filterParams.page_size,
+ })
+ );
const memoizedTableDataResults = useMemo(
() => tableData?.results ?? [],
[tableData?.results]
);
- const { mutate: refreshTasksMutation, isPending: isRefreshTasksPending } =
- useRefreshJobsMutation();
- const { address: oracle_address } = useParams<{ address: string }>();
-
- const [paginationState, setPaginationState] = useState({
- pageIndex: 0,
- pageSize: 5,
- });
-
- const refreshData = useCallback(() => {
- refreshTasksMutation({ oracle_address: oracle_address ?? '' });
- }, [refreshTasksMutation, oracle_address]);
+ const columns = useGetMyJobsColumns();
useEffect(() => {
if (paginationState.pageSize === 5 || paginationState.pageSize === 10) {
setPageParams(paginationState.pageIndex, paginationState.pageSize);
}
- }, [paginationState, setPageParams]);
+ }, [paginationState.pageIndex, paginationState.pageSize, setPageParams]);
useEffect(() => {
- setPaginationState({
- pageIndex: filterParams.page,
- pageSize: filterParams.page_size,
+ setPaginationState((currentPaginationState) => {
+ if (
+ currentPaginationState.pageIndex === filterParams.page &&
+ currentPaginationState.pageSize === filterParams.page_size
+ ) {
+ return currentPaginationState;
+ }
+
+ return {
+ pageIndex: filterParams.page,
+ pageSize: filterParams.page_size,
+ };
});
}, [filterParams.page, filterParams.page_size]);
- useEffect(() => {
- return () => {
- resetFilterParams();
- };
- }, [resetFilterParams]);
-
const table = useMaterialReactTable({
- columns: getColumnsDefinition({
- refreshData,
- isRefreshTasksPending,
- chainIdsEnabled,
- }),
+ columns,
data: memoizedTableDataResults,
state: {
- isLoading: tableStatus === 'pending',
- showAlertBanner: tableStatus === 'error',
+ isLoading: isPending,
+ showAlertBanner: isError,
pagination: paginationState,
},
- enablePagination: Boolean(tableData?.total_pages),
+ enablePagination: !!tableData?.total_pages,
manualPagination: true,
- onPaginationChange: (updater) => {
- setPaginationState(updater);
- },
+ onPaginationChange: setPaginationState,
muiPaginationProps: {
+ rowsPerPageOptions: [5, 10],
SelectProps: {
sx: {
+ '.MuiSelect-select': {
+ color: colorPalette.text.auxiliary100,
+ },
'.MuiSelect-icon': {
':hover': {
backgroundColor: 'blue',
},
- fill: colorPalette.text.primary,
+ fill: colorPalette.text.auxiliary100,
},
},
},
- rowsPerPageOptions: [5, 10],
+ },
+ muiBottomToolbarProps: {
+ sx: {
+ bgcolor: colorPalette.background.paper,
+ boxShadow: 'none',
+ color: colorPalette.text.auxiliary100,
+ },
},
pageCount: tableData?.total_pages ?? -1,
rowCount: tableData?.total_results,
enableColumnActions: false,
enableColumnFilters: false,
enableSorting: false,
- renderTopToolbar: () => (
- {
- setSearchEscrowAddress(address);
- }}
- />
- ),
+ renderTopToolbar: false,
+ muiTablePaperProps: {
+ sx: {
+ boxShadow: 'none',
+ },
+ },
+ muiTableHeadProps: {
+ sx: {
+ backgroundColor: colorPalette.background.default,
+ },
+ },
+ muiTableHeadRowProps: {
+ sx: {
+ backgroundColor: 'inherit',
+ boxShadow: 'none',
+ },
+ },
muiTableHeadCellProps: {
sx: {
- borderColor: colorPalette.paper.text,
+ paddingTop: '8px',
+ paddingBottom: '8px',
+ paddingLeft: '12px',
+ paddingRight: '12px',
+ borderColor: colorPalette.background.paper,
+ color: colorPalette.text.auxiliary200,
+ typography: 'body1',
+ fontWeight: 500,
+ '& .Mui-TableHeadCell-Content': {
+ justifyContent: 'center',
+ textAlign: 'center',
+ },
},
},
muiTableBodyCellProps: {
+ align: 'center',
sx: {
- borderColor: colorPalette.paper.text,
+ textAlign: 'center',
+ borderBottom: `1px solid ${colorPalette.border.main}`,
},
},
- muiTablePaperProps: {
+ muiTableBodyRowProps: {
sx: {
- boxShadow: '0px 2px 2px 0px #E9EBFA80',
+ bgcolor: colorPalette.background.paper,
+ borderBottom: `1px solid ${colorPalette.border.main}`,
+ '&:last-of-type': {
+ borderBottom: 'none',
+ },
},
},
...(isDarkMode ? createTableDarkMode(colorPalette) : {}),
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/status-chip.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/status-chip.tsx
deleted file mode 100644
index 0d7523530e..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/status-chip.tsx
+++ /dev/null
@@ -1,27 +0,0 @@
-import Box from '@mui/material/Box';
-import Typography from '@mui/material/Typography';
-import { useColorMode } from '@/shared/contexts/color-mode';
-import { getChipStatusColor } from '../../utils';
-import { type MyJob } from '../../../schemas';
-
-export function StatusChip({ status }: Readonly<{ status: MyJob['status'] }>) {
- const { colorPalette } = useColorMode();
-
- return (
-
-
- {status}
-
-
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/status-filter.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/status-filter.tsx
new file mode 100644
index 0000000000..cd3fcc93c7
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/desktop/status-filter.tsx
@@ -0,0 +1,73 @@
+import { useTranslation } from 'react-i18next';
+import { Button } from '@/shared/components/ui/button';
+import { Stack } from '@mui/material';
+import { MyJobStatus, type StatusFilterType } from '../../../types';
+import { useColorMode } from '@/shared/contexts/color-mode';
+import { useMyJobsFilterStore } from '../../../hooks';
+
+export const STATUS_FILTER_OPTIONS = [
+ {
+ labelKey: 'worker.jobs.statusFilter.all',
+ value: '',
+ },
+ {
+ labelKey: 'worker.jobs.statusFilter.inProgress',
+ value: MyJobStatus.ACTIVE,
+ },
+ {
+ labelKey: 'worker.jobs.statusFilter.completed',
+ value: MyJobStatus.COMPLETED,
+ },
+] as const satisfies {
+ labelKey: string;
+ value: StatusFilterType;
+}[];
+
+export function StatusFilter() {
+ const { t } = useTranslation();
+ const { colorPalette } = useColorMode();
+ const { filterParams, setFilterParams } = useMyJobsFilterStore();
+ const activeStatus = filterParams.status ?? '';
+
+ return (
+
+ {STATUS_FILTER_OPTIONS.map((option) => {
+ const isActive = option.value === activeStatus;
+ return (
+
+ );
+ })}
+
+ );
+}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/index.ts
index cb17920d1b..083493610a 100644
--- 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
@@ -1,2 +1 @@
-export * from './my-jobs-filter-modal';
export * from './my-jobs-list-mobile';
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-card-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-card-mobile.tsx
new file mode 100644
index 0000000000..49fae4ae2f
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-card-mobile.tsx
@@ -0,0 +1,163 @@
+import { Chip, Paper, Stack, Typography } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+
+import {
+ EvmAddress,
+ MyJobsTableActions,
+ RewardAmount,
+} from '../../../components';
+import { type MyJob } from '../../../schemas';
+import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode';
+import { JobType } from '@/modules/smart-contracts/EthKVStore/config';
+import { TimeUntil } from '../time-until';
+import { ChainIcon } from '@/shared/components/ui/chain-icon';
+import {
+ JobExpiryTimeIcon,
+ JobStatusIcon,
+ OracleAddressIcon,
+ OracleRewardIcon,
+} from '@/shared/components/ui/icons';
+
+const Row = ({
+ label,
+ icon,
+ children,
+}: {
+ label: string;
+ icon: React.ReactNode;
+ children: React.ReactNode;
+}) => {
+ const { colorPalette } = useColorMode();
+ return (
+
+ {icon}
+
+ {label}:{' '}
+
+ {children}
+
+ );
+};
+
+export function MyJobsCardMobile({ job }: { job: MyJob }) {
+ const { colorPalette } = useColorMode();
+ const { t } = useTranslation();
+
+ const jobTypeLabel = t(`jobTypeLabels.${job.job_type as JobType}`);
+
+ const hasUrl = !!job.url;
+
+ return (
+
+
+
+
+
+
+
+ }
+ >
+
+
+
+ }
+ >
+
+
+
+
+
}
+ >
+
+ {job.status.toLowerCase()}
+
+
+
+ }
+ >
+
+
+
+ {hasUrl && (
+
+
+
+ )}
+
+ );
+}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-expires-at-sort-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-expires-at-sort-mobile.tsx
deleted file mode 100644
index 3ae5d921aa..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-expires-at-sort-mobile.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import Typography from '@mui/material/Typography';
-import { t } from 'i18next';
-import { useColorMode } from '@/shared/contexts/color-mode';
-import { Sorting } from '../../../components';
-import { useMyJobsFilterStore } from '../../../hooks';
-import { SortDirection, SortField } from '../../../types';
-
-export function MyJobsExpiresAtSortMobile() {
- const { setFilterParams, filterParams } = useMyJobsFilterStore();
- const { colorPalette } = useColorMode();
-
- return (
-
- {t('worker.jobs.expiresAt')}
-
- }
- fromHighestSelected={
- filterParams.sort_field === SortField.EXPIRES_AT &&
- filterParams.sort === SortDirection.DESC
- }
- sortFromHighest={() => {
- setFilterParams({
- sort: SortDirection.DESC,
- sort_field: SortField.EXPIRES_AT,
- });
- }}
- fromLowestSelected={
- filterParams.sort_field === SortField.EXPIRES_AT &&
- filterParams.sort === SortDirection.ASC
- }
- sortFromLowest={() => {
- setFilterParams({
- sort: SortDirection.ASC,
- sort_field: SortField.EXPIRES_AT,
- });
- }}
- clear={() => {
- setFilterParams({
- sort: undefined,
- sort_field: undefined,
- });
- }}
- />
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-filter-modal.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-filter-modal.tsx
deleted file mode 100644
index ef40385c0f..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-filter-modal.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import Box from '@mui/material/Box';
-import Drawer from '@mui/material/Drawer';
-import CssBaseline from '@mui/material/CssBaseline';
-import { Divider, IconButton, Stack, Typography } from '@mui/material';
-import { useTranslation } from 'react-i18next';
-import CloseIcon from '@mui/icons-material/Close';
-import { HumanLogoIcon } from '@/shared/components/ui/icons';
-import { useColorMode } from '@/shared/contexts/color-mode';
-import { useHandleMainNavIconClick } from '@/shared/hooks/use-handle-main-nav-icon-click';
-import { MyJobsNetworkFilterMobile } from './my-jobs-network-filter-mobile';
-import { MyJobsJobTypeFilterMobile } from './my-jobs-job-type-filter-mobile';
-import { MyJobsStatusFilterMobile } from './my-jobs-status-filter-mobile';
-import { MyJobsExpiresAtSortMobile } from './my-jobs-expires-at-sort-mobile';
-import { MyJobsRewardAmountSortMobile } from './my-jobs-reward-amount-sort-mobile';
-
-interface MyJobsFilterModalProps {
- chainIdsEnabled: number[];
- close: () => void;
-}
-export function MyJobsFilterModal({
- chainIdsEnabled,
- close,
-}: Readonly) {
- const handleMainNavIconClick = useHandleMainNavIconClick();
- const { colorPalette } = useColorMode();
- const { t } = useTranslation();
-
- return (
-
-
-
-
- {
- handleMainNavIconClick();
- }}
- >
-
-
-
-
-
-
-
-
- {t('worker.jobs.mobileFilterDrawer.filters')}
-
-
-
- {t('worker.jobs.mobileFilterDrawer.sortBy')}
-
-
-
-
- {t('worker.jobs.mobileFilterDrawer.filters')}
-
-
-
- {t('worker.jobs.network')}
-
-
-
-
-
-
-
- {t('worker.jobs.jobType')}
-
-
-
-
-
-
- {t('worker.jobs.status')}
-
-
-
-
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-job-type-filter-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-job-type-filter-mobile.tsx
deleted file mode 100644
index 7e9ba0eb96..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-job-type-filter-mobile.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { useTranslation } from 'react-i18next';
-import { Filtering } from '@/shared/components/ui/table/table-header-menu/filtering';
-import { JOB_TYPES } from '@/shared/consts';
-import { useMyJobsFilterStore } from '../../../hooks';
-
-export function MyJobsJobTypeFilterMobile() {
- const { t } = useTranslation();
- const { setFilterParams, filterParams } = useMyJobsFilterStore();
-
- return (
- {
- setFilterParams({
- job_type: undefined,
- page: 0,
- });
- }}
- filteringOptions={JOB_TYPES.map((jobType) => ({
- name: t(`jobTypeLabels.${jobType}`),
- option: jobType,
- }))}
- isChecked={(option) => option === filterParams.job_type}
- setFiltering={(jobType) => {
- setFilterParams({
- job_type: jobType,
- page: 0,
- });
- }}
- />
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-list-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-list-mobile.tsx
index fa38a01fca..a38d313de6 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-list-mobile.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-list-mobile.tsx
@@ -1,211 +1,51 @@
-import { Grid, List, Paper, Stack, Typography } from '@mui/material';
+import { Stack } from '@mui/material';
import { useTranslation } from 'react-i18next';
-import { useEffect } from 'react';
-import { useParams } from 'react-router-dom';
+
import { Button } from '@/shared/components/ui/button';
-import { FiltersButtonIcon, RefreshIcon } from '@/shared/components/ui/icons';
import { Loader } from '@/shared/components/ui/loader';
import { Alert } from '@/shared/components/ui/alert';
-import { getNetworkName } from '@/modules/smart-contracts/get-network-name';
import { getErrorMessageForError } from '@/shared/errors';
-import { ListItem } from '@/shared/components/ui/list-item';
-import { useColorMode } from '@/shared/contexts/color-mode';
-import { Chip } from '@/shared/components/ui/chip';
-import type { JobType } from '@/modules/smart-contracts/EthKVStore/config';
-import { formatDate } from '@/shared/helpers/date';
import { useCombinePages } from '@/shared/hooks';
-import {
- EscrowAddressSearchForm,
- EvmAddress,
- RewardAmount,
- MyJobsTableActions,
-} from '../../../components';
-import {
- useMyJobsFilterStore,
- useJobsFilterStore,
- useInfiniteGetMyJobsData,
-} from '../../../hooks';
-import { useRefreshJobsMutation } from '../../hooks';
-import { getChipStatusColor } from '../../utils';
+import { useMyJobsFilterStore, useInfiniteGetMyJobsData } from '../../../hooks';
import { type MyJob } from '../../../schemas';
-import { useMyJobFilterModal } from '../../hooks/use-my-jobs-filter-modal';
+import { MyJobsCardMobile } from './my-jobs-card-mobile';
export function MyJobsListMobile() {
- const { colorPalette } = useColorMode();
- const { filterParams, setPageParams, resetFilterParams } =
- useMyJobsFilterStore();
-
const { t } = useTranslation();
- const {
- data: tableData,
- status: tableStatus,
- isError: isTableError,
- error: tableError,
- fetchNextPage,
- hasNextPage,
- } = useInfiniteGetMyJobsData();
-
- const { mutate: refreshTasksMutation, isPending: isRefreshTasksPending } =
- useRefreshJobsMutation();
- const { setSearchEscrowAddress } = useJobsFilterStore();
- const { address: oracle_address } = useParams<{ address: string }>();
+ const { filterParams, setPageParams } = useMyJobsFilterStore();
+ const { data, isPending, isError, error, fetchNextPage, hasNextPage } =
+ useInfiniteGetMyJobsData();
- const allPages = useCombinePages(tableData, filterParams.page);
- const { openModal } = useMyJobFilterModal();
-
- useEffect(() => {
- return () => {
- resetFilterParams();
- };
- }, [resetFilterParams]);
+ const allPages = useCombinePages(data);
return (
- <>
-
-
-
-
-
-
-
-
-
-
-
- {isTableError ? (
-
- {getErrorMessageForError(tableError)}
-
- ) : null}
- {tableStatus === 'pending' ? (
-
-
-
- ) : null}
- {allPages.map((d) => {
- return (
-
-
-
-
-
-
-
-
-
- {d.expires_at ? formatDate(d.expires_at) : ''}
-
-
-
-
-
-
-
-
-
- {getNetworkName(d.chain_id)}
-
-
-
-
- {d.status}
-
- }
- />
-
-
-
-
-
-
-
-
-
-
-
- );
+
+ {isError && (
+
+ {getErrorMessageForError(error)}
+
+ )}
+ {isPending && (
+
+
+
+ )}
+ {!isPending &&
+ !isError &&
+ allPages.map((d) => {
+ return ;
})}
- {hasNextPage ? (
-
- ) : null}
-
- >
+ {hasNextPage && (
+
+ )}
+
);
}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-network-filter-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-network-filter-mobile.tsx
deleted file mode 100644
index 550df051f4..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-network-filter-mobile.tsx
+++ /dev/null
@@ -1,32 +0,0 @@
-import { Filtering } from '@/shared/components/ui/table/table-header-menu/filtering';
-import { useMyJobsFilterStore, useGetAllNetworks } from '../../../hooks';
-
-interface MyJobsNetworkFilterMobileProps {
- chainIdsEnabled: number[];
-}
-
-export function MyJobsNetworkFilterMobile({
- chainIdsEnabled,
-}: Readonly) {
- const { setFilterParams, filterParams } = useMyJobsFilterStore();
- const { allNetworks } = useGetAllNetworks(chainIdsEnabled);
-
- return (
- {
- setFilterParams({
- chain_id: undefined,
- page: 0,
- });
- }}
- filteringOptions={allNetworks}
- isChecked={(option) => option === filterParams.chain_id}
- setFiltering={(chainId) => {
- setFilterParams({
- chain_id: chainId,
- page: 0,
- });
- }}
- />
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-reward-amount-sort-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-reward-amount-sort-mobile.tsx
deleted file mode 100644
index 3362987213..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-reward-amount-sort-mobile.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import Typography from '@mui/material/Typography';
-import { t } from 'i18next';
-import { useColorMode } from '@/shared/contexts/color-mode';
-import { Sorting } from '../../../components';
-import { useMyJobsFilterStore } from '../../../hooks';
-import { SortDirection, SortField } from '../../../types';
-
-export function MyJobsRewardAmountSortMobile() {
- const { setFilterParams, filterParams } = useMyJobsFilterStore();
- const { colorPalette } = useColorMode();
- const isRewardAmountSortSelected =
- filterParams.sort_field === SortField.REWARD_AMOUNT;
-
- return (
-
- {t('worker.jobs.rewardAmount')}
-
- }
- fromHighestSelected={
- isRewardAmountSortSelected && filterParams.sort === SortDirection.DESC
- }
- sortFromHighest={() => {
- setFilterParams({
- sort: SortDirection.DESC,
- sort_field: SortField.REWARD_AMOUNT,
- });
- }}
- fromLowestSelected={
- isRewardAmountSortSelected && filterParams.sort === SortDirection.ASC
- }
- sortFromLowest={() => {
- setFilterParams({
- sort: SortDirection.ASC,
- sort_field: SortField.REWARD_AMOUNT,
- });
- }}
- clear={() => {
- setFilterParams({
- sort: undefined,
- sort_field: undefined,
- });
- }}
- />
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-status-filter-mobile.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-status-filter-mobile.tsx
deleted file mode 100644
index d1199971d8..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/mobile/my-jobs-status-filter-mobile.tsx
+++ /dev/null
@@ -1,30 +0,0 @@
-import capitalize from 'lodash/capitalize';
-import { Filtering } from '@/shared/components/ui/table/table-header-menu/filtering';
-import { useMyJobsFilterStore } from '../../../hooks';
-import { MyJobStatus } from '../../../types';
-
-export function MyJobsStatusFilterMobile() {
- const { setFilterParams, filterParams } = useMyJobsFilterStore();
-
- return (
- {
- setFilterParams({
- status: undefined,
- page: 0,
- });
- }}
- filteringOptions={Object.values(MyJobStatus).map((status) => ({
- name: capitalize(status),
- option: status,
- }))}
- isChecked={(status) => status === filterParams.status}
- setFiltering={(status) => {
- setFilterParams({
- status,
- page: 0,
- });
- }}
- />
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/time-until.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/time-until.tsx
new file mode 100644
index 0000000000..d2edfe0aa9
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/components/time-until.tsx
@@ -0,0 +1,71 @@
+import { useEffect, useMemo, useState } from 'react';
+import { useTranslation } from 'react-i18next';
+import { formatDistanceToNow } from 'date-fns';
+import { enUS } from 'date-fns/locale';
+
+const SECOND_IN_MS = 1000;
+
+const DATE_FNS_LOCALES = {
+ en: enUS,
+} as const;
+
+export function TimeUntil({ date }: { date?: string | null }) {
+ const { t, i18n } = useTranslation();
+ const [now, setNow] = useState(Date.now());
+
+ const dateFnsLocale = useMemo(() => {
+ const language = i18n.resolvedLanguage ?? i18n.language;
+ const baseLanguage = language.split(
+ '-'
+ )[0] as keyof typeof DATE_FNS_LOCALES;
+
+ return DATE_FNS_LOCALES[baseLanguage] ?? enUS;
+ }, [i18n.language, i18n.resolvedLanguage]);
+
+ const targetDate = useMemo(() => {
+ if (!date) {
+ return null;
+ }
+
+ const parsedDate = new Date(date);
+
+ return Number.isNaN(parsedDate.getTime()) ? null : parsedDate;
+ }, [date]);
+
+ useEffect(() => {
+ const targetTime = targetDate?.getTime();
+
+ if (targetTime == null || targetTime <= Date.now()) {
+ return;
+ }
+
+ setNow(Date.now());
+
+ const intervalId = setInterval(() => {
+ const nextNow = Date.now();
+
+ setNow(nextNow);
+
+ if (nextNow >= targetTime) {
+ clearInterval(intervalId);
+ }
+ }, SECOND_IN_MS);
+
+ return () => {
+ clearInterval(intervalId);
+ };
+ }, [targetDate]);
+
+ if (targetDate === null || targetDate.getTime() <= now) {
+ return t('worker.jobs.expired');
+ }
+
+ return (
+ <>
+ {formatDistanceToNow(targetDate, {
+ includeSeconds: true,
+ locale: dateFnsLocale,
+ })}
+ >
+ );
+}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-get-my-jobs-columns.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-get-my-jobs-columns.tsx
new file mode 100644
index 0000000000..9ae2d16ff7
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-get-my-jobs-columns.tsx
@@ -0,0 +1,141 @@
+import { useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Chip, Grid, Typography } from '@mui/material';
+import { type MRT_ColumnDef } from 'material-react-table';
+
+import type { JobType } from '@/modules/smart-contracts/EthKVStore/config';
+import { EvmAddress, RewardAmount, MyJobsTableActions } from '../../components';
+import { type MyJob } from '../../schemas';
+import { useColorMode } from '@/shared/contexts/color-mode';
+import { useIsMobile } from '@/shared/hooks/use-is-mobile';
+import { ChainIcon } from '@/shared/components/ui/chain-icon';
+import { TimeUntil } from '../components/time-until';
+
+const COL_SIZE_SM = 50;
+const COL_SIZE = 100;
+const COL_SIZE_MD = 150;
+
+export const useGetMyJobsColumns = (): MRT_ColumnDef[] => {
+ const { colorPalette } = useColorMode();
+ const isMobile = useIsMobile();
+ const { t } = useTranslation();
+
+ return useMemo(
+ () => [
+ {
+ accessorKey: 'escrow_address',
+ header: t('worker.jobs.address'),
+ size: COL_SIZE,
+ enableSorting: true,
+ Cell: (props) => {
+ return ;
+ },
+ },
+ {
+ accessorKey: 'network',
+ header: t('worker.jobs.network'),
+ size: COL_SIZE_SM,
+ Cell: (props) => {
+ return ;
+ },
+ },
+ {
+ accessorKey: 'reward_amount',
+ header: t('worker.jobs.reward'),
+ size: COL_SIZE_MD,
+ enableSorting: true,
+ Cell: (props) => {
+ const { reward_amount, reward_token } = props.row.original;
+ return (
+
+ );
+ },
+ },
+ {
+ accessorKey: 'job_type',
+ header: t('worker.jobs.jobType'),
+ size: COL_SIZE,
+ enableSorting: true,
+ Cell: ({ row }) => {
+ const label = t(`jobTypeLabels.${row.original.job_type as JobType}`);
+ return (
+
+ );
+ },
+ },
+ {
+ accessorKey: 'expires_at',
+ header: t('worker.jobs.expiryTime'),
+ size: COL_SIZE_MD,
+ enableSorting: true,
+ Cell: (props) => {
+ return (
+
+
+
+ );
+ },
+ },
+ {
+ accessorKey: 'status',
+ header: t('worker.jobs.status'),
+ size: COL_SIZE_SM,
+ enableSorting: true,
+ Cell: (props) => {
+ return (
+
+ {props.row.original.status.toLowerCase()}
+
+ );
+ },
+ },
+ {
+ accessorKey: 'assignment_id',
+ header: t('worker.jobs.action'),
+ size: COL_SIZE_MD,
+ enableSorting: true,
+ Cell: (props) => (
+
+
+
+ ),
+ },
+ ],
+ [colorPalette, t, isMobile]
+ );
+};
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-my-jobs-filter-modal.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-my-jobs-filter-modal.tsx
deleted file mode 100644
index 40a0c9c1be..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/hooks/use-my-jobs-filter-modal.tsx
+++ /dev/null
@@ -1,21 +0,0 @@
-import { useModal } from '@/shared/contexts/modal-context';
-import { useGetUiConfig } from '@/shared/hooks';
-import { MyJobsFilterModal } from '../components/mobile/my-jobs-filter-modal';
-
-export function useMyJobFilterModal() {
- const { openModal, closeModal } = useModal();
- const { data: uiConfigData } = useGetUiConfig();
-
- return {
- openModal: () => {
- openModal({
- content: (
-
- ),
- });
- },
- };
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/my-jobs-view.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/my-jobs-view.tsx
deleted file mode 100644
index 8e2500625b..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/my-jobs-view.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import { useIsMobile } from '@/shared/hooks';
-import { MyJobsTable } from './components/desktop';
-import { MyJobsListMobile } from './components/mobile';
-
-export function MyJobsView({ chainIdsEnabled }: { chainIdsEnabled: number[] }) {
- const isMobile = useIsMobile();
-
- return isMobile ? (
-
- ) : (
-
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/my-jobs.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/my-jobs.page.tsx
new file mode 100644
index 0000000000..cb814e3e61
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/my-jobs.page.tsx
@@ -0,0 +1,88 @@
+import { useEffect } from 'react';
+import { useTranslation } from 'react-i18next';
+import { Stack, Typography } from '@mui/material';
+import RefreshIcon from '@mui/icons-material/Refresh';
+
+import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode';
+import { useIsMobile } from '@/shared/hooks/use-is-mobile';
+import { Button } from '@/shared/components/ui/button';
+import { MyJobsListMobile } from './components/mobile/my-jobs-list-mobile';
+import { MyJobsTable } from './components/desktop/my-jobs-table';
+import { useRefreshJobsMutation } from './hooks';
+import { StatusFilter } from './components/desktop/status-filter';
+import { MyJobsFilters } from './components/desktop/my-jobs-filters';
+import { useMyJobsFilterStore } from '../hooks';
+import { JobsSwitcherMobile } from '@/router/components/layout/protected/jobs-switcher-mobile';
+
+export function MyJobsPage() {
+ const { colorPalette } = useColorMode();
+ const isMobile = useIsMobile();
+ const { t } = useTranslation();
+
+ const {
+ filterParams: { oracle_address },
+ resetFilterParams,
+ } = useMyJobsFilterStore();
+
+ useEffect(() => {
+ return () => {
+ resetFilterParams();
+ };
+ }, [resetFilterParams]);
+
+ const { mutate: refreshTasksMutation, isPending: isRefreshPending } =
+ useRefreshJobsMutation();
+
+ return (
+
+ {isMobile && (
+
+
+
+ )}
+
+
+ {t('worker.jobs.myJobs')}
+
+
+
+
+
+ {!isMobile && }
+
+
+
+
+ {isMobile ? : }
+
+
+ );
+}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/__tests__/get-chip-status-color.test.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/__tests__/get-chip-status-color.test.ts
deleted file mode 100644
index 900e4ea26a..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/__tests__/get-chip-status-color.test.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { describe, expect, it } from 'vitest';
-import { colorPalette } from '@/shared/styles/color-palette';
-import { getChipStatusColor } from '../get-chip-status-color';
-import { MyJobStatus, UNKNOWN_JOB_STATUS } from '../../../types';
-
-describe('getChipStatusColor Function', () => {
- it('should return the secondary main color for ACTIVE status', () => {
- const result = getChipStatusColor(MyJobStatus.ACTIVE, colorPalette);
- expect(result).toBe(colorPalette.secondary.main);
- });
-
- it('should return the success main color for COMPLETED status', () => {
- const result = getChipStatusColor(MyJobStatus.COMPLETED, colorPalette);
- expect(result).toBe(colorPalette.success.main);
- });
-
- it('should return the error main color for unknown status', () => {
- const result = getChipStatusColor(UNKNOWN_JOB_STATUS, colorPalette);
- expect(result).toBe(colorPalette.error.main);
- });
-});
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/get-chip-status-color.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/get-chip-status-color.ts
deleted file mode 100644
index 6fc926bbf8..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/get-chip-status-color.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { type ColorPalette } from '@/shared/styles/color-palette';
-import { MyJobStatus, type UNKNOWN_JOB_STATUS } from '../../types';
-
-export function getChipStatusColor(
- status: MyJobStatus | typeof UNKNOWN_JOB_STATUS,
- colorPalette: ColorPalette
-) {
- switch (status) {
- case MyJobStatus.ACTIVE:
- return colorPalette.secondary.main;
- case MyJobStatus.COMPLETED:
- return colorPalette.success.main;
- default:
- return colorPalette.error.main;
- }
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/index.ts
deleted file mode 100644
index bfddc6caac..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/my-jobs/utils/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './get-chip-status-color';
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/types.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/types.ts
index d2317b7cf0..977e6b9e3a 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/types.ts
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/types.ts
@@ -13,6 +13,8 @@ export enum MyJobStatus {
REJECTED = 'REJECTED',
}
+export type StatusFilterType = '' | MyJobStatus.ACTIVE | MyJobStatus.COMPLETED;
+
export const UNKNOWN_JOB_STATUS = 'UNKNOWN';
export enum SortField {
diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/components/modal-states.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/components/modal-states.tsx
deleted file mode 100644
index 929d1219c2..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/profile/components/modal-states.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-import CheckIcon from '@mui/icons-material/CheckCircle';
-import { Loader } from '@/shared/components/ui/loader';
-import Box from '@mui/material/Box';
-import Typography from '@mui/material/Typography';
-import CloseIcon from '@mui/icons-material/Cancel';
-
-export function ModalLoading() {
- return (
-
-
-
- );
-}
-
-export function ModalSuccess({ children }: { children: React.ReactNode }) {
- return (
- <>
-
-
-
- {children}
- >
- );
-}
-
-export function ModalError({ message }: { message: string }) {
- return (
- <>
-
-
-
-
- {message}
-
- >
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/profile/views/profile.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/profile/views/profile.page.tsx
index 70285faf85..d705d0edbd 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/profile/views/profile.page.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/profile/views/profile.page.tsx
@@ -1,5 +1,5 @@
-import { Box, IconButton, Stack, Tooltip, Typography } from '@mui/material';
-import { useEffect, useRef, useState, type MouseEvent } from 'react';
+import { useEffect } from 'react';
+import { Box, Stack, Typography } from '@mui/material';
import { t } from 'i18next';
import { Link, useNavigate } from 'react-router-dom';
@@ -11,22 +11,16 @@ import {
} from '@/shared/hooks/use-notification';
import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode';
import { ProfileData } from '../components/profile-data';
-import {
- CheckmarkIcon,
- CopyIcon,
- LogoutIcon,
-} from '@/shared/components/ui/icons';
+import { CheckmarkIcon, LogoutIcon } from '@/shared/components/ui/icons';
import { shortenEscrowAddress } from '@/shared/helpers/evm';
import { Button } from '@/shared/components/ui/button';
import { browserAuthProvider } from '@/shared/contexts/browser-auth-provider';
import { routerPaths } from '@/router/router-paths';
import { BackButton } from '@/shared/components/ui/page-card/back-button';
import { useIsMobile } from '@/shared/hooks/use-is-mobile';
+import { CopyToClipboardButton } from '@/shared/components/ui/copy-to-clipboard-button';
export function WorkerProfilePage() {
- const [isCopied, setIsCopied] = useState(false);
- const timeoutRef = useRef(null);
-
const { user } = useAuthenticatedUser();
const { colorPalette } = useColorMode();
const { isConnected, initializing, web3ProviderMutation } =
@@ -39,22 +33,6 @@ export function WorkerProfilePage() {
navigate(routerPaths.worker.jobsDiscovery);
};
- const handleCopyClick = (e: MouseEvent) => {
- if (isCopied) return;
-
- e.stopPropagation();
- navigator.clipboard.writeText(user.wallet_address ?? '');
- setIsCopied(true);
-
- if (timeoutRef.current) {
- clearTimeout(timeoutRef.current);
- }
-
- timeoutRef.current = setTimeout(() => {
- setIsCopied(false);
- }, 1500);
- };
-
const handleSignOut = () => {
browserAuthProvider.signOut({
callback: () => {
@@ -63,15 +41,6 @@ export function WorkerProfilePage() {
});
};
- useEffect(() => {
- return () => {
- if (timeoutRef.current) {
- clearTimeout(timeoutRef.current);
- timeoutRef.current = null;
- }
- };
- }, []);
-
useEffect(() => {
if (initializing) return;
@@ -98,7 +67,7 @@ export function WorkerProfilePage() {
]);
return (
-
+
{shortenEscrowAddress(user.wallet_address ?? '', 9, 8)}
-
-
-
-
-
+
diff --git a/packages/apps/human-app/frontend/src/modules/worker/services/oracles.service.ts b/packages/apps/human-app/frontend/src/modules/worker/services/oracles.service.ts
index 518006885e..bd4f5fd2b2 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/services/oracles.service.ts
+++ b/packages/apps/human-app/frontend/src/modules/worker/services/oracles.service.ts
@@ -1,7 +1,6 @@
import { z } from 'zod';
import { ApiClientError, authorizedHumanAppApiClient } from '@/api';
import { env } from '@/shared/env';
-import { MainnetChains, TestnetChains } from '@/modules/smart-contracts/chains';
const apiPaths = {
oracles: '/oracles',
@@ -13,6 +12,10 @@ const OracleSchema = z.object({
role: z.string(),
name: z.string(),
url: z.string(),
+ nTasks: z.number(),
+ minRewardAmount: z.string(),
+ maxRewardAmount: z.string(),
+ rewardToken: z.string(),
jobTypes: z.array(z.string()),
registrationNeeded: z.boolean().optional().nullable(),
registrationInstructions: z.string().optional().nullable(),
@@ -26,41 +29,13 @@ export type Oracle = OracleBase & {
name: string;
};
-const isTestnet = env.VITE_NETWORK === 'testnet';
-
-const H_CAPTCHA_ORACLE: Oracle = {
- address: env.VITE_H_CAPTCHA_ORACLE_ADDRESS,
- chainId: isTestnet ? TestnetChains[0].chainId : MainnetChains[0].chainId,
- jobTypes: env.VITE_H_CAPTCHA_ORACLE_TASK_TYPES,
- role: env.VITE_H_CAPTCHA_ORACLE_ROLE,
- url: env.VITE_H_CAPTCHA_ORACLE_ANNOTATION_TOOL,
- name: 'hCaptcha',
- registrationNeeded: false,
-};
-
-async function getOracles(selectedJobTypes: string[]) {
+async function getOracles() {
try {
- const params = selectedJobTypes.length
- ? { selected_job_types: selectedJobTypes }
- : undefined;
-
- const queryParams = params ?? {};
-
let oracles: Oracle[] = [];
- if (
- selectedJobTypes.length === 0 ||
- selectedJobTypes.some((t) => H_CAPTCHA_ORACLE.jobTypes.includes(t))
- ) {
- oracles.push(H_CAPTCHA_ORACLE);
- }
-
if (env.VITE_FEATURE_FLAG_JOBS_DISCOVERY) {
const results = await authorizedHumanAppApiClient.get(
- apiPaths.oracles,
- {
- queryParams,
- }
+ apiPaths.oracles
);
if (Array.isArray(results)) {
diff --git a/packages/apps/human-app/frontend/src/router/components/drawer-menu-items/drawer-menu-items-worker.tsx b/packages/apps/human-app/frontend/src/router/components/drawer-menu-items/drawer-menu-items-worker.tsx
deleted file mode 100644
index 1c28472e9b..0000000000
--- a/packages/apps/human-app/frontend/src/router/components/drawer-menu-items/drawer-menu-items-worker.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { t } from 'i18next';
-import type { UserData } from '@/modules/auth/context/auth-context';
-import { routerPaths } from '@/router/router-paths';
-import { KycStatus } from '@/modules/worker/profile/types';
-
-export const workerDrawerTopMenuItems = (user: UserData | null) => {
- return [
- {
- label: t('components.DrawerNavigation.jobs'),
- link: routerPaths.worker.jobsDiscovery,
- disabled: !user?.wallet_address || user.kyc_status !== KycStatus.APPROVED,
- },
- ];
-};
diff --git a/packages/apps/human-app/frontend/src/router/components/drawer-menu-items/index.ts b/packages/apps/human-app/frontend/src/router/components/drawer-menu-items/index.ts
deleted file mode 100644
index 8dd1acfaae..0000000000
--- a/packages/apps/human-app/frontend/src/router/components/drawer-menu-items/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from './drawer-menu-items-worker';
diff --git a/packages/apps/human-app/frontend/src/router/components/footer.tsx b/packages/apps/human-app/frontend/src/router/components/footer.tsx
index 8eb703de07..79ae8ef89e 100644
--- a/packages/apps/human-app/frontend/src/router/components/footer.tsx
+++ b/packages/apps/human-app/frontend/src/router/components/footer.tsx
@@ -6,13 +6,9 @@ import { useColorMode } from '@/shared/contexts/color-mode';
interface FooterProps {
displayChatIcon?: boolean;
- isProtected?: boolean;
}
-export function Footer({
- displayChatIcon = true,
- isProtected = false,
-}: FooterProps) {
+export function Footer({ displayChatIcon = true }: FooterProps) {
const { colorPalette, isDarkMode } = useColorMode();
const { t } = useTranslation();
@@ -24,8 +20,8 @@ export function Footer({
container
sx={{
width: '100%',
- px: { xs: 3, md: 0 },
- py: { xs: 2, md: isProtected ? 0 : 3 },
+ px: { xs: 3, md: 2 },
+ py: { xs: 2, md: 3 },
bgcolor: { xs: colorPalette.background.paper, md: 'transparent' },
borderTop: {
xs: `1px solid ${colorPalette.border.main}`,
diff --git a/packages/apps/human-app/frontend/src/router/components/index.ts b/packages/apps/human-app/frontend/src/router/components/index.ts
index 0a514e542c..c78a32e4dc 100644
--- a/packages/apps/human-app/frontend/src/router/components/index.ts
+++ b/packages/apps/human-app/frontend/src/router/components/index.ts
@@ -1,3 +1,2 @@
-export * from './drawer-menu-items';
export * from './layout/protected';
export * from './layout/unprotected';
diff --git a/packages/apps/human-app/frontend/src/router/components/layout/protected/desktop-aside-bar.tsx b/packages/apps/human-app/frontend/src/router/components/layout/protected/desktop-aside-bar.tsx
index 051f917544..363945c19d 100644
--- a/packages/apps/human-app/frontend/src/router/components/layout/protected/desktop-aside-bar.tsx
+++ b/packages/apps/human-app/frontend/src/router/components/layout/protected/desktop-aside-bar.tsx
@@ -1,17 +1,47 @@
+import {
+ Box,
+ IconButton,
+ List,
+ ListItem,
+ ListItemButton,
+ Stack,
+} from '@mui/material';
+import { useTranslation } from 'react-i18next';
+import { Link, useLocation } from 'react-router-dom';
+
import { ProfileData } from '@/modules/worker/profile/components/profile-data';
+import { routerPaths } from '@/router/router-paths';
import { Button } from '@/shared/components/ui/button';
import { ColorModeSwitch } from '@/shared/components/ui/dark-mode-switch';
-import { HelpIcon, HumanLogoNavbarIcon } from '@/shared/components/ui/icons';
+import {
+ HelpIcon,
+ HumanLogoNavbarIcon,
+ TriangleIcon,
+} from '@/shared/components/ui/icons';
import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode';
import { useHandleMainNavIconClick } from '@/shared/hooks/use-handle-main-nav-icon-click';
-import { Box, IconButton, Stack } from '@mui/material';
-import { useTranslation } from 'react-i18next';
+
+const menuItems = [
+ {
+ labelKey: 'components.DrawerNavigation.availableJobs',
+ href: routerPaths.worker.jobsDiscovery,
+ },
+ {
+ labelKey: 'components.DrawerNavigation.myJobs',
+ href: routerPaths.worker.myJobs,
+ },
+] as const;
export function DesktopAsideBar() {
- const { colorPalette } = useColorMode();
+ const { colorPalette, isDarkMode } = useColorMode();
const { t } = useTranslation();
+ const { pathname } = useLocation();
const handleMainNavIconClick = useHandleMainNavIconClick();
+ const activeLinkGradient = isDarkMode
+ ? 'linear-gradient(90deg, rgba(212, 207, 255, 0.10) 0%, rgba(37, 29, 71, 0.10) 90%)'
+ : 'linear-gradient(90deg, rgba(50, 10, 141, 0.10) 0%, rgba(255, 255, 255, 0.10) 90%)';
+
return (
-
+
+
+ {menuItems.map((item) => {
+ const { href, labelKey } = item;
+ const isActive = pathname === href;
+ return (
+
+
+ {isActive && }
+ {t(labelKey)}
+
+
+ );
+ })}
+
+
+ {BUTTONS.map((button) => {
+ const isActive = pathname === button.path;
+ return (
+
+ );
+ })}
+
+ );
+}
diff --git a/packages/apps/human-app/frontend/src/router/components/layout/protected/layout.tsx b/packages/apps/human-app/frontend/src/router/components/layout/protected/layout.tsx
index 5e6449aece..213a11e448 100644
--- a/packages/apps/human-app/frontend/src/router/components/layout/protected/layout.tsx
+++ b/packages/apps/human-app/frontend/src/router/components/layout/protected/layout.tsx
@@ -1,9 +1,8 @@
-import { Box, Stack, styled } from '@mui/material';
-import { useEffect, useRef, useState, type ReactElement } from 'react';
+import { useRef } from 'react';
import { Outlet, useLocation } from 'react-router-dom';
+import { Box, Stack, styled } from '@mui/material';
import { useIsMobile } from '@/shared/hooks/use-is-mobile';
-import { useIsHCaptchaLabelingPage } from '@/shared/hooks/use-is-hcaptcha-labeling-page';
import { GovernanceBanner } from '@/modules/governance-banner/components/governance-banner';
import { Footer } from '../../footer';
import { Navbar } from './navbar';
@@ -13,59 +12,22 @@ import { ProfileBottomTray } from '@/modules/worker/profile/components/profile-b
import { MOBILE_BOTTOM_TRAY_HEIGHT } from '@/shared/consts';
import { routerPaths } from '@/router/router-paths';
-const Main = styled('main', {
- shouldForwardProp: (prop) => prop !== 'open',
-})<{
- open?: boolean;
-}>(({ theme, open }) => ({
- width: '100%',
+const Main = styled('main')({
display: 'flex',
flex: '1',
- transition: theme.transitions.create('margin', {
- easing: theme.transitions.easing.sharp,
- duration: theme.transitions.duration.leavingScreen,
- }),
- ...(open && {
- transition: theme.transitions.create('margin', {
- easing: theme.transitions.easing.easeOut,
- duration: theme.transitions.duration.enteringScreen,
- }),
- }),
-}));
+ width: '100%',
+});
-export function ProtectedLayout({
- renderHCaptchaStatisticsDrawer,
- renderGovernanceBanner,
-}: {
- renderHCaptchaStatisticsDrawer?: (isOpen: boolean) => ReactElement;
- renderGovernanceBanner?: boolean;
-}) {
+export function ProtectedLayout() {
const layoutElementRef = useRef(null);
- const isHCaptchaLabelingPage = useIsHCaptchaLabelingPage();
+
const isMobile = useIsMobile();
- const [drawerOpen, setDrawerOpen] = useState(!isMobile);
- const [hcaptchaDrawerOpen, setHcaptchaDrawerOpen] = useState(false);
const { colorPalette } = useColorMode();
const location = useLocation();
- const toggleUserStatsDrawer = isHCaptchaLabelingPage
- ? () => {
- setHcaptchaDrawerOpen((state) => !state);
- }
- : undefined;
const isProfilePage = location.pathname === routerPaths.worker.profile;
const isBottomTrayVisible = isMobile && !isProfilePage;
- useEffect(() => {
- if (isMobile) {
- setHcaptchaDrawerOpen(false);
- setDrawerOpen(false);
- } else {
- setHcaptchaDrawerOpen(false);
- setDrawerOpen(true);
- }
- }, [isMobile]);
-
return (
- {isMobile && (
-
- )}
-
+ {isMobile && }
{!isMobile && }
- {isHCaptchaLabelingPage && renderHCaptchaStatisticsDrawer
- ? renderHCaptchaStatisticsDrawer(hcaptchaDrawerOpen)
- : null}
-
+
- {renderGovernanceBanner && }
+
{isBottomTrayVisible && }
-
+
);
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 1fde484268..fcd04711bf 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
@@ -1,65 +1,15 @@
import { Stack } from '@mui/material';
-// import MenuIcon from '@mui/icons-material/Menu';
-// import CloseIcon from '@mui/icons-material/Close';
import { t } from 'i18next';
+
import { HumanLogoNavbarIcon } from '@/shared/components/ui/icons';
import { Button } from '@/shared/components/ui/button';
-//import { useIsHCaptchaLabelingPage } from '@/shared/hooks/use-is-hcaptcha-labeling-page';
import { useColorMode } from '@/shared/contexts/color-mode';
import { useHandleMainNavIconClick } from '@/shared/hooks/use-handle-main-nav-icon-click';
import { ColorModeSwitch } from '@/shared/components/ui/dark-mode-switch';
-interface NavbarProps {
- open: boolean;
- setOpen: (open: boolean) => void;
- toggleUserStatsDrawer?: () => void;
- userStatsDrawerOpen: boolean;
-}
-
-export function Navbar({
- setOpen,
- open,
- // userStatsDrawerOpen,
- // toggleUserStatsDrawer,
-}: NavbarProps) {
- const handleMainNavIconClick = useHandleMainNavIconClick();
+export function Navbar() {
const { colorPalette } = useColorMode();
- //const isHCaptchaLabelingPage = useIsHCaptchaLabelingPage();
-
- // let iconButton = null;
- // if (open) {
- // iconButton = (
- // {
- // setOpen(false);
- // }}
- // >
- //
- //
- // );
- // } else if (userStatsDrawerOpen) {
- // iconButton = (
- // {
- // if (toggleUserStatsDrawer) {
- // toggleUserStatsDrawer();
- // }
- // }}
- // >
- //
- //
- // );
- // } else {
- // iconButton = (
- // {
- // setOpen(true);
- // }}
- // >
- //
- //
- // );
- // }
+ const handleMainNavIconClick = useHandleMainNavIconClick();
return (
- {/*
- {isHCaptchaLabelingPage && toggleUserStatsDrawer ? (
-
- ) : null}
- {iconButton}
- */}
);
}
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 530a26bc7f..b781b810be 100644
--- a/packages/apps/human-app/frontend/src/router/router-paths.ts
+++ b/packages/apps/human-app/frontend/src/router/router-paths.ts
@@ -12,6 +12,7 @@ export const routerPaths = {
profile: '/worker/profile',
jobsDiscovery: '/worker/jobs-discovery',
jobs: '/worker/jobs-discovery',
+ myJobs: '/worker/my-jobs',
HcaptchaLabeling: '/worker/hcaptcha-labeling',
enableLabeler: '/worker/enable-labeler',
registrationInExchangeOracle: '/worker/registration-in-exchange-oracle',
diff --git a/packages/apps/human-app/frontend/src/router/router.tsx b/packages/apps/human-app/frontend/src/router/router.tsx
index f938b1b921..9f6b069caa 100644
--- a/packages/apps/human-app/frontend/src/router/router.tsx
+++ b/packages/apps/human-app/frontend/src/router/router.tsx
@@ -8,7 +8,6 @@ import {
import { RequireAuth } from '@/modules/auth/providers/require-auth';
import { RequireWalletConnect } from '@/shared/contexts/wallet-connect';
import { RequireWeb3Auth } from '@/modules/auth-web3/providers/require-web3-auth';
-import { UserStatsDrawer } from '@/modules/worker/hcaptcha-labeling';
import { routerPaths } from './router-paths';
import { ProtectedLayout, UnprotectedLayout } from './components';
@@ -38,12 +37,7 @@ export function Router() {
- (
-
- )}
- renderGovernanceBanner
- />
+
}
key={routerProps.path}
diff --git a/packages/apps/human-app/frontend/src/router/routes.tsx b/packages/apps/human-app/frontend/src/router/routes.tsx
index d6b0fd9fba..af21c4b803 100644
--- a/packages/apps/human-app/frontend/src/router/routes.tsx
+++ b/packages/apps/human-app/frontend/src/router/routes.tsx
@@ -4,7 +4,6 @@ import { env } from '@/shared/env';
import { RegistrationPage } from '@/modules/worker/oracle-registration';
import {
HcaptchaLabelingPage,
- //UserStatsAccordion,
EnableLabelerPage,
} from '@/modules/worker/hcaptcha-labeling';
import {
@@ -13,6 +12,7 @@ import {
} from '@/modules/worker/email-verification';
import { SignInWorkerPage } from '@/modules/signin/worker';
import { JobsDiscoveryPage } from '@/modules/worker/jobs-discovery';
+import { MyJobsPage } from '@/modules/worker/jobs/my-jobs/my-jobs.page';
import { WorkerProfilePage } from '@/modules/worker/profile';
import { SignUpWorkerPage } from '@/modules/signup/worker';
import { OperatorProfilePage } from '@/modules/operator/profile';
@@ -81,6 +81,12 @@ export const protectedRoutes: {
element: ,
},
},
+ {
+ routerProps: {
+ path: routerPaths.worker.myJobs,
+ element: ,
+ },
+ },
...(env.VITE_FEATURE_FLAG_JOBS_DISCOVERY
? [
{
diff --git a/packages/apps/human-app/frontend/src/shared/components/expiration-modal.tsx b/packages/apps/human-app/frontend/src/shared/components/expiration-modal.tsx
index 302469ed2f..2ebb4e4e41 100644
--- a/packages/apps/human-app/frontend/src/shared/components/expiration-modal.tsx
+++ b/packages/apps/human-app/frontend/src/shared/components/expiration-modal.tsx
@@ -4,7 +4,6 @@ import { useNavigate } from 'react-router-dom';
import { Button } from '@/shared/components/ui/button';
import { routerPaths } from '@/router/router-paths';
import { browserAuthProvider } from '@/shared/contexts/browser-auth-provider';
-import { breakpoints } from '@/shared/styles/breakpoints';
import { useModal } from '@/shared/contexts/modal-context';
import { useColorMode } from '../contexts/color-mode';
@@ -24,10 +23,7 @@ export function ExpirationModal() {
sx={{
alignItems: 'center',
justifyContent: 'center',
- p: 15,
- [breakpoints.mobile]: {
- p: 2,
- },
+ p: { xs: 2, md: 15 },
}}
>
) {
+ const { colorPalette } = useColorMode();
+
const handleClose = useCallback(() => {
if (isLoading) return;
onClose();
@@ -28,6 +31,9 @@ export function BaseDrawer({
open={open}
onClose={handleClose}
anchor="bottom"
+ sx={{
+ zIndex: (theme) => theme.zIndex.drawer,
+ }}
slotProps={{
backdrop: {
sx: {
@@ -43,6 +49,7 @@ export function BaseDrawer({
borderTopRightRadius: '16px',
border: 'none',
height: '75dvh',
+ bgcolor: `${colorPalette.background.paper} !important`,
...sx,
},
},
@@ -54,11 +61,12 @@ export function BaseDrawer({
position: 'absolute',
top: 16,
right: 16,
- p: 0,
+ p: 0.5,
+ color: colorPalette.text.auxiliary100,
...closeButtonSx,
}}
>
-
+
{children}
diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/chain-icon.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/chain-icon.tsx
new file mode 100644
index 0000000000..2407afb444
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/shared/components/ui/chain-icon.tsx
@@ -0,0 +1,33 @@
+import { ReactElement } from 'react';
+import { ChainId } from '@human-protocol/sdk/src/enums';
+
+import { EthereumIcon, PolygonIcon } from './icons';
+import { Stack, Tooltip } from '@mui/material';
+import { getNetworkName } from '@/modules/smart-contracts/get-network-name';
+
+export const CHAIN_ICONS: Partial> = {
+ [ChainId.MAINNET]: ,
+ [ChainId.SEPOLIA]: ,
+ [ChainId.POLYGON]: ,
+ [ChainId.POLYGON_AMOY]: ,
+};
+
+export function ChainIcon({ chainId }: { chainId: number }) {
+ if (!CHAIN_ICONS[chainId]) {
+ return null;
+ }
+
+ return (
+
+
+ {CHAIN_ICONS[chainId]}
+
+
+ );
+}
diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/copy-to-clipboard-button.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/copy-to-clipboard-button.tsx
new file mode 100644
index 0000000000..6b4fb99271
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/shared/components/ui/copy-to-clipboard-button.tsx
@@ -0,0 +1,60 @@
+import { useEffect, useRef, useState, type MouseEvent } from 'react';
+import { IconButton, SxProps, Theme, Tooltip } from '@mui/material';
+import { t } from 'i18next';
+
+import { CopyIcon } from './icons';
+
+type Props = {
+ value: string;
+ sx?: SxProps;
+};
+
+export function CopyToClipboardButton({ value, sx }: Props) {
+ const [isCopied, setIsCopied] = useState(false);
+ const timeoutRef = useRef(null);
+
+ const handleCopyClick = (e: MouseEvent) => {
+ if (isCopied) return;
+
+ e.stopPropagation();
+ navigator.clipboard.writeText(value ?? '');
+ setIsCopied(true);
+
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ }
+
+ timeoutRef.current = setTimeout(() => {
+ setIsCopied(false);
+ }, 1500);
+ };
+
+ useEffect(() => {
+ return () => {
+ if (timeoutRef.current) {
+ clearTimeout(timeoutRef.current);
+ timeoutRef.current = null;
+ }
+ };
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/icons.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/icons.tsx
index f05cdb0c89..600145becb 100644
--- a/packages/apps/human-app/frontend/src/shared/components/ui/icons.tsx
+++ b/packages/apps/human-app/frontend/src/shared/components/ui/icons.tsx
@@ -22,8 +22,6 @@ import SunIconLight from '@/assets/icons/sun.svg';
import MoonIconDark from '@/assets/icons-dark-mode/moon.svg';
import MoonIconLight from '@/assets/icons/moon.svg';
import { useColorMode } from '@/shared/contexts/color-mode';
-import CopyIconLight from '@/assets/icons/content-copy.svg';
-import CopyIconDark from '@/assets/icons-dark-mode/content-copy.svg';
import EditIconLight from '@/assets/icons/edit-icon.svg';
import EditIconDark from '@/assets/icons-dark-mode/edit-icon.svg';
import DeleteIconLight from '@/assets/icons/delete-icon.svg';
@@ -33,6 +31,12 @@ import VeriffIconDark from '@/assets/icons-dark-mode/veriff.svg';
import HourglassIconLight from '@/assets/icons/hourglass.svg';
import HourglassIconDark from '@/assets/icons-dark-mode/hourglass.svg';
import LogoutIcon from '@/assets/icons/logout.svg';
+import TriangleIcon from '@/assets/icons/triangle.svg';
+import HcaptchaIconAsset from '@/assets/icons/hcaptcha.svg';
+import HcaptchaDisabledIconLight from '@/assets/icons/hcaptcha-disabled-icon.svg';
+import HcaptchaDisabledIconDark from '@/assets/icons-dark-mode/hcaptcha-disabled-icon.svg';
+import EthereumIcon from '@/assets/icons/ethereum-icon.svg';
+import PolygonIcon from '@/assets/icons/polygon-icon.svg';
function HumanLogoIcon() {
const { isDarkMode } = useColorMode();
@@ -78,10 +82,6 @@ function MoonIcon() {
const { isDarkMode } = useColorMode();
return isDarkMode ? : ;
}
-function CopyIcon() {
- const { isDarkMode } = useColorMode();
- return isDarkMode ? : ;
-}
function EditIcon() {
const { isDarkMode } = useColorMode();
return isDarkMode ? : ;
@@ -90,6 +90,26 @@ function DeleteIcon() {
const { isDarkMode } = useColorMode();
return isDarkMode ? : ;
}
+function VeriffIcon() {
+ const { isDarkMode } = useColorMode();
+ return isDarkMode ? : ;
+}
+function HourglassIcon() {
+ const { isDarkMode } = useColorMode();
+ return isDarkMode ? : ;
+}
+function HcaptchaIcon() {
+ return ;
+}
+function HcaptchaDisabledIcon() {
+ const { isDarkMode } = useColorMode();
+ return isDarkMode ? (
+
+ ) : (
+
+ );
+}
+
function InboxIcon(props: SvgIconProps) {
return (
@@ -142,14 +162,183 @@ function SuccessIcon(props: SvgIconProps) {
);
}
-function VeriffIcon() {
- const { isDarkMode } = useColorMode();
- return isDarkMode ? : ;
+function CopyIcon(props: SvgIconProps) {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
}
-function HourglassIcon() {
- const { isDarkMode } = useColorMode();
- return isDarkMode ? : ;
+function OracleAddressIcon(props: SvgIconProps) {
+ return (
+
+
+
+
+ );
+}
+
+function OracleRewardIcon(props: SvgIconProps) {
+ return (
+
+
+
+
+ );
+}
+
+function MenuIcon(props: SvgIconProps) {
+ return (
+
+
+
+
+
+ );
+}
+
+function FilterIcon(props: SvgIconProps) {
+ return (
+
+
+
+ );
+}
+
+function JobExpiryTimeIcon(props: SvgIconProps) {
+ return (
+
+
+
+
+
+ );
+}
+
+function JobStatusIcon(props: SvgIconProps) {
+ return (
+
+
+
+
+
+ );
}
export {
@@ -172,4 +361,15 @@ export {
VeriffIcon,
HourglassIcon,
LogoutIcon,
+ TriangleIcon,
+ OracleAddressIcon,
+ OracleRewardIcon,
+ MenuIcon,
+ HcaptchaIcon,
+ HcaptchaDisabledIcon,
+ FilterIcon,
+ JobExpiryTimeIcon,
+ JobStatusIcon,
+ EthereumIcon,
+ PolygonIcon,
};
diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/modal/base-modal.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/modal/base-modal.tsx
index 3438e89d93..414f165a2e 100644
--- a/packages/apps/human-app/frontend/src/shared/components/ui/modal/base-modal.tsx
+++ b/packages/apps/human-app/frontend/src/shared/components/ui/modal/base-modal.tsx
@@ -1,5 +1,4 @@
import { type PropsWithChildren, useCallback } from 'react';
-
import CloseIcon from '@mui/icons-material/Close';
import {
IconButton,
@@ -9,6 +8,8 @@ import {
type Theme,
} from '@mui/material';
+import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode';
+
type Props = {
open: boolean;
onClose: () => void;
@@ -25,6 +26,8 @@ export function BaseModal({
closeButtonSx,
children,
}: PropsWithChildren) {
+ const { colorPalette } = useColorMode();
+
const handleClose = useCallback(() => {
if (isLoading) return;
onClose();
@@ -52,7 +55,8 @@ export function BaseModal({
@@ -67,10 +72,10 @@ export function BaseModal({
disabled={isLoading}
onClick={handleClose}
sx={{
- p: 0,
- color: 'neutral.100',
+ p: 0.5,
+ color: colorPalette.text.auxiliary100,
position: 'absolute',
- top: 48,
+ top: 32,
right: 32,
'&:hover': {
bgcolor: 'unset',
@@ -85,5 +90,3 @@ export function BaseModal({
);
}
-
-export default BaseModal;
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 360a2c3867..d5d91e614e 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
@@ -17,6 +17,7 @@ export function GlobalModal() {
open={open}
onClose={!disableClose ? closeModal : undefined}
onTransitionExited={onTransitionExited}
+ sx={{ zIndex: (theme) => theme.zIndex.modal }}
slotProps={{
paper: {
sx: {
diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/no-records.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/no-records.tsx
index 190638947e..cf7b5fae01 100644
--- a/packages/apps/human-app/frontend/src/shared/components/ui/no-records.tsx
+++ b/packages/apps/human-app/frontend/src/shared/components/ui/no-records.tsx
@@ -1,4 +1,5 @@
import { Grid } from '@mui/material';
+import { t } from 'i18next';
import { useColorMode } from '@/shared/contexts/color-mode';
export function NoRecords() {
@@ -7,13 +8,13 @@ export function NoRecords() {
return (
- No records to display
+ {t('components.noRecords')}
);
}
diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/page-card/page-card-error.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/page-card/page-card-error.tsx
index 0c38cc4720..aeedae8122 100644
--- a/packages/apps/human-app/frontend/src/shared/components/ui/page-card/page-card-error.tsx
+++ b/packages/apps/human-app/frontend/src/shared/components/ui/page-card/page-card-error.tsx
@@ -1,54 +1,37 @@
-import { Grid, Stack } from '@mui/material';
import { useNavigate } from 'react-router-dom';
import { t } from 'i18next';
+import { Stack } from '@mui/material';
+
import { Button } from '@/shared/components/ui/button';
-import { routerPaths } from '@/router/router-paths';
import { Alert } from '@/shared/components/ui/alert';
-import { useColorMode } from '@/shared/contexts/color-mode';
-import { commonDarkPageCardStyles, commonPageCardStyles } from './styles';
import { type ErrorMessageProps } from './types';
+import { commonStyles } from './styles';
export function PageCardError({
errorMessage,
cardMaxWidth = '100%',
}: ErrorMessageProps) {
- const { isDarkMode } = useColorMode();
const navigate = useNavigate();
- const commonStyleForTheme = isDarkMode
- ? commonDarkPageCardStyles
- : commonPageCardStyles;
-
const sx = cardMaxWidth
- ? { ...commonStyleForTheme, maxWidth: cardMaxWidth }
- : commonStyleForTheme;
+ ? { ...commonStyles, maxWidth: cardMaxWidth }
+ : commonStyles;
return (
-
+
{errorMessage}
-
-
-
-
-
+
+
);
}
diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/page-card/page-card-loader.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/page-card/page-card-loader.tsx
index 578fa382bc..ca6fbdb51e 100644
--- a/packages/apps/human-app/frontend/src/shared/components/ui/page-card/page-card-loader.tsx
+++ b/packages/apps/human-app/frontend/src/shared/components/ui/page-card/page-card-loader.tsx
@@ -1,30 +1,24 @@
-import { Grid } from '@mui/material';
+import { Stack } from '@mui/material';
import { Loader } from '@/shared/components/ui/loader';
-import { useColorMode } from '@/shared/contexts/color-mode';
import { useIsMobile } from '@/shared/hooks';
-import { commonDarkPageCardStyles, commonPageCardStyles } from './styles';
+import { commonStyles } from './styles';
import { type CommonProps } from './types';
export function PageCardLoader({ cardMaxWidth = '100%' }: CommonProps) {
- const { isDarkMode } = useColorMode();
const isMobile = useIsMobile();
- const commonStyleForTheme = isDarkMode
- ? commonDarkPageCardStyles
- : commonPageCardStyles;
-
const sx = cardMaxWidth
? {
- ...commonStyleForTheme,
+ ...commonStyles,
maxWidth: cardMaxWidth,
}
- : commonStyleForTheme;
+ : commonStyles;
return (
-
+
-
+
);
}
diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/page-card/page-card.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/page-card/page-card.tsx
index 8d6bd44b34..f398af4c63 100644
--- a/packages/apps/human-app/frontend/src/shared/components/ui/page-card/page-card.tsx
+++ b/packages/apps/human-app/frontend/src/shared/components/ui/page-card/page-card.tsx
@@ -1,7 +1,7 @@
-import { Grid, Typography } from '@mui/material';
+import { Grid, Stack, Typography } from '@mui/material';
import { useNavigate } from 'react-router-dom';
-import { useColorMode } from '@/shared/contexts/color-mode';
-import { commonDarkPageCardStyles, commonPageCardStyles } from './styles';
+
+import { commonStyles } from './styles';
import { BackButton } from './back-button';
type NavigationTarget = string | (() => void);
@@ -26,7 +26,6 @@ export function PageCard({
childrenMaxWidth = '486px',
showBackButton = true,
}: PageCardProps) {
- const { isDarkMode } = useColorMode();
const navigate = useNavigate();
const contentStyles = {
@@ -51,10 +50,9 @@ export function PageCard({
};
return (
-
@@ -124,6 +122,6 @@ export function PageCard({
-
+
);
}
diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/page-card/styles.ts b/packages/apps/human-app/frontend/src/shared/components/ui/page-card/styles.ts
index e26f4b48a7..e084791850 100644
--- a/packages/apps/human-app/frontend/src/shared/components/ui/page-card/styles.ts
+++ b/packages/apps/human-app/frontend/src/shared/components/ui/page-card/styles.ts
@@ -1,29 +1,7 @@
-import type { SxProps, Theme } from '@mui/material';
-import { breakpoints } from '@/shared/styles/breakpoints';
-import { colorPalette as constColorPalette } from '@/shared/styles/color-palette';
-import { darkColorPalette as constDarkColorPalette } from '@/shared/styles/dark-color-palette';
-
-export const commonPageCardStyles: SxProps = {
- flexDirection: 'column',
+export const commonStyles = {
justifyContent: 'center',
alignItems: 'center',
- borderRadius: '20px',
- minHeight: '70vh',
maxWidth: '1600px',
width: '100%',
- background: constColorPalette.white,
-};
-
-export const commonDarkPageCardStyles: SxProps = {
- flexDirection: 'column',
- justifyContent: 'center',
- alignItems: 'center',
- borderRadius: '20px',
- minHeight: '70vh',
- maxWidth: '1600px',
- width: '100%',
- background: constDarkColorPalette.paper.main,
- [breakpoints.mobile]: {
- background: constDarkColorPalette.backgroundColor,
- },
+ background: 'transparent',
};
diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/table-button.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/table-button.tsx
index 3b2d2209ce..ed203a4301 100644
--- a/packages/apps/human-app/frontend/src/shared/components/ui/table-button.tsx
+++ b/packages/apps/human-app/frontend/src/shared/components/ui/table-button.tsx
@@ -8,7 +8,7 @@ export function TableButton(props: CustomButtonProps) {
size="small"
type="button"
variant="contained"
- color="secondary"
+ color="accent"
sx={{
paddingTop: '0.4rem',
paddingBottom: '0.4rem',
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
deleted file mode 100644
index a0dfa345b0..0000000000
--- a/packages/apps/human-app/frontend/src/shared/components/ui/table/table-header-cell.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import React, { useState } from 'react';
-import Popover from '@mui/material/Popover';
-import type { TableCellBaseProps } from '@mui/material/TableCell';
-import { type IconType, TextHeaderWithIcon } from '../text-header-with-icon';
-
-type CommonProps = TableCellBaseProps & {
- popoverContent: React.ReactElement;
-};
-
-type PropsWithIcon = CommonProps & {
- headerText: string;
- iconType: IconType;
-};
-type PropsWithoutIcon = CommonProps & {
- headerText?: never;
- iconType?: never;
-};
-
-type HeaderCellProps = PropsWithoutIcon | PropsWithIcon;
-
-export function TableHeaderCell({
- popoverContent,
- headerText,
- iconType,
-}: HeaderCellProps) {
- const [anchorEl, setAnchorEl] = useState(null);
-
- const handleClick = (
- event:
- | React.MouseEvent
- | React.KeyboardEvent
- ) => {
- setAnchorEl(event.currentTarget);
- };
-
- const handleClose = () => {
- setAnchorEl(null);
- };
-
- const open = Boolean(anchorEl);
- const id = open ? 'simple-popover' : undefined;
-
- const getHeader = () => {
- if (!iconType) {
- return {headerText}
;
- }
-
- return (
-
-
-
- );
- };
-
- return (
- <>
- {getHeader()}
-
- {popoverContent}
-
- >
- );
-}
-
-TableHeaderCell.displayName = 'TableHeaderCell';
diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/table/table-header-menu/filtering.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/table/table-header-menu/filtering.tsx
deleted file mode 100644
index ed71180808..0000000000
--- a/packages/apps/human-app/frontend/src/shared/components/ui/table/table-header-menu/filtering.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import Checkbox from '@mui/material/Checkbox';
-import Typography from '@mui/material/Typography';
-import List from '@mui/material/List';
-import Divider from '@mui/material/Divider';
-import ListItem from '@mui/material/ListItem';
-import { t } from 'i18next';
-import { useColorMode } from '@/shared/contexts/color-mode';
-
-interface FilteringOption {
- name: string;
- option: T;
-}
-
-interface FilteringProps {
- filteringOptions: FilteringOption[];
- isChecked: (option: T) => boolean;
- setFiltering: (option: T) => void;
- clear: () => void;
- showTitle?: boolean;
- showClearButton?: boolean;
-}
-
-export function Filtering({
- filteringOptions,
- isChecked,
- setFiltering,
- clear,
- showTitle = false,
- showClearButton = false,
-}: Readonly>) {
- const { colorPalette } = useColorMode();
-
- return (
-
- {showTitle ? (
-
- {t('components.table.filter')}
-
- ) : null}
- {filteringOptions.map(({ option, name }) => {
- return (
-
- {
- if (isChecked(option)) {
- clear();
- return;
- }
- setFiltering(option);
- }}
- sx={{ pl: 0, ':hover': { background: 'none' } }}
- />
-
-
- {name}
-
-
-
- );
- })}
- {showClearButton ? (
- <>
-
-
- {
- clear();
- }}
- >
- {t('components.table.clearBtn')}
-
-
- >
- ) : null}
-
- );
-}
diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/table/table-header-menu/sorting.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/table/table-header-menu/sorting.tsx
deleted file mode 100644
index 835197d3fd..0000000000
--- a/packages/apps/human-app/frontend/src/shared/components/ui/table/table-header-menu/sorting.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { Divider, Typography } from '@mui/material';
-import List from '@mui/material/List';
-import ListItemText from '@mui/material/ListItemText';
-import { t } from 'i18next';
-import { useColorMode } from '@/shared/contexts/color-mode';
-
-interface SortingMenuProps {
- sortingOptions: { label: string; sortCallback: () => void }[];
- clear: () => void;
-}
-
-export function Sorting({ sortingOptions, clear }: SortingMenuProps) {
- const { colorPalette } = useColorMode();
-
- return (
-
-
- {t('components.table.sort')}
-
- {sortingOptions.map(({ label, sortCallback }) => {
- return (
- {
- sortCallback();
- }}
- sx={{ padding: '0.2rem 0.5rem', cursor: 'pointer' }}
- >
-
- {label}
-
-
- );
- })}
-
-
- {
- clear();
- }}
- >
- {t('components.table.clearBtn')}
-
-
-
- );
-}
diff --git a/packages/apps/human-app/frontend/src/shared/components/ui/table/table-query-context.tsx b/packages/apps/human-app/frontend/src/shared/components/ui/table/table-query-context.tsx
deleted file mode 100644
index 50bc898ca6..0000000000
--- a/packages/apps/human-app/frontend/src/shared/components/ui/table/table-query-context.tsx
+++ /dev/null
@@ -1,66 +0,0 @@
-import React, { createContext, useState } from 'react';
-import type {
- MRT_SortingState,
- MRT_PaginationState,
-} from 'material-react-table';
-
-const DEFAULT_PAGINATION = {
- pageIndex: 1,
- pageSize: 5,
-};
-
-export interface TableQueryContext {
- actions: {
- setSorting: React.Dispatch>;
- setPagination: React.Dispatch>;
- setFiltering: React.Dispatch>;
- };
- fields: {
- sorting: MRT_SortingState;
- pagination: MRT_PaginationState;
- filtering: string[];
- };
-}
-
-export const TableQueryContext = createContext({
- actions: {
- setSorting: () => undefined,
- setPagination: () => undefined,
- setFiltering: () => undefined,
- },
- fields: {
- sorting: [],
- pagination: DEFAULT_PAGINATION,
- filtering: [],
- },
-});
-
-export function TableQueryContextProvider({
- children,
-}: {
- children: React.ReactNode;
-}) {
- const [sorting, setSorting] = useState([]);
- const [pagination, setPagination] =
- useState(DEFAULT_PAGINATION);
- const [filtering, setFiltering] = useState([]);
-
- return (
-
- {children}
-
- );
-}
diff --git a/packages/apps/human-app/frontend/src/shared/consts.ts b/packages/apps/human-app/frontend/src/shared/consts.ts
index abfe8a21bc..7ba98cc59e 100644
--- a/packages/apps/human-app/frontend/src/shared/consts.ts
+++ b/packages/apps/human-app/frontend/src/shared/consts.ts
@@ -1,4 +1,4 @@
import { JobType } from '@/modules/smart-contracts/EthKVStore/config';
export const JOB_TYPES = Object.values(JobType);
-export const MOBILE_BOTTOM_TRAY_HEIGHT = '110px';
+export const MOBILE_BOTTOM_TRAY_HEIGHT = '90px';
diff --git a/packages/apps/human-app/frontend/src/shared/env.ts b/packages/apps/human-app/frontend/src/shared/env.ts
index 3be4fb921a..aa054c35f7 100644
--- a/packages/apps/human-app/frontend/src/shared/env.ts
+++ b/packages/apps/human-app/frontend/src/shared/env.ts
@@ -31,13 +31,6 @@ const envSchema = z.object({
}),
VITE_NETWORK: z.enum(['mainnet', 'testnet']),
VITE_GOVERNANCE_URL: z.string(),
- VITE_H_CAPTCHA_ORACLE_ANNOTATION_TOOL: z.string(),
- VITE_H_CAPTCHA_ORACLE_ROLE: z.string(),
- VITE_H_CAPTCHA_ORACLE_ADDRESS: z.string(),
- VITE_H_CAPTCHA_ORACLE_TASK_TYPES: z.string().transform((value) => {
- const jobTypesArray = value.split(',');
- return jobTypesArray;
- }),
VITE_FEATURE_FLAG_JOBS_DISCOVERY: z
.string()
.prefault('false')
diff --git a/packages/apps/human-app/frontend/src/shared/hooks/index.ts b/packages/apps/human-app/frontend/src/shared/hooks/index.ts
index 6f64178930..7803ff3958 100644
--- a/packages/apps/human-app/frontend/src/shared/hooks/index.ts
+++ b/packages/apps/human-app/frontend/src/shared/hooks/index.ts
@@ -1,7 +1,6 @@
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';
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
index 74a5ac32be..f36063a20e 100644
--- 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
@@ -1,21 +1,10 @@
-import { useEffect, useState } from 'react';
+import { useMemo } from 'react';
export function useCombinePages(
- tableData: { pages: { results: T[] }[] } | undefined,
- page: number
+ tableData: { pages: { results: T[] }[] } | undefined
) {
- 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;
+ return useMemo(
+ () => tableData?.pages.flatMap((page) => page.results) ?? [],
+ [tableData?.pages]
+ );
}
diff --git a/packages/apps/human-app/frontend/src/shared/hooks/use-is-hcaptcha-labeling-page.ts b/packages/apps/human-app/frontend/src/shared/hooks/use-is-hcaptcha-labeling-page.ts
deleted file mode 100644
index a5b0f95f61..0000000000
--- a/packages/apps/human-app/frontend/src/shared/hooks/use-is-hcaptcha-labeling-page.ts
+++ /dev/null
@@ -1,7 +0,0 @@
-import { useLocation } from 'react-router-dom';
-import { routerPaths } from '@/router/router-paths';
-
-export function useIsHCaptchaLabelingPage() {
- const location = useLocation();
- return location.pathname === routerPaths.worker.HcaptchaLabeling;
-}
diff --git a/packages/apps/human-app/frontend/src/shared/i18n/en.json b/packages/apps/human-app/frontend/src/shared/i18n/en.json
index f4b9c5984b..ad9dd8ffca 100644
--- a/packages/apps/human-app/frontend/src/shared/i18n/en.json
+++ b/packages/apps/human-app/frontend/src/shared/i18n/en.json
@@ -61,6 +61,8 @@
"jobs": "Tasks",
"captchaLabeling": "hCaptcha labeling",
"jobsDiscovery": "Jobs discovery",
+ "availableJobs": "Available Jobs",
+ "myJobs": "My Jobs",
"profile": "Profile",
"help": "Help",
"logout": "Log Out"
@@ -91,7 +93,8 @@
"reload": "Reload",
"goHome": "Home Page"
},
- "copyToClipboard": "Copied"
+ "copyToClipboard": "Copied",
+ "noRecords": "No records to display"
},
"homepage": {
"humanApp": "HUMAN App",
@@ -243,6 +246,16 @@
"abandoned": "Abandoned"
}
},
+ "oraclesList": {
+ "jobOracles": "Job Oracles",
+ "address": "Address",
+ "reward": "Reward",
+ "tasks": "Tasks",
+ "task": "Task",
+ "exploreTasks": "Explore Tasks",
+ "source": "source",
+ "sources": "sources"
+ },
"oraclesTable": {
"annotationTool": "Annotation tool",
"oracleAddress": "Oracle address",
@@ -253,6 +266,16 @@
"gettingOracles": "There was an error while fetching oracles, please try again later."
}
},
+ "hcaptchaWidget": {
+ "title": "hCaptcha task available!",
+ "titleDisabled": "No hCaptcha task available",
+ "description": "Complete image labeling challenges and earn rewards by helping train privacy-preserving hCaptcha models.",
+ "shortDescription": "Complete image labeling challenges and earn rewards.",
+ "descriptionDisabled": "You have reached the daily limit for the hCaptcha tasks, please come back later.",
+ "startTask": "Start Task",
+ "nextAvailable": "Next Available in",
+ "nextIn": "Next in"
+ },
"registrationInExchangeOracle": {
"requiredMessage": "This oracle requires a registration process. Click on the button below to see the registration tutorial:",
"instructionsButton": "See registration tutorial",
@@ -260,25 +283,42 @@
"completeButton": "Complete"
},
"jobs": {
- "successFullyAssignedJob": "Successfully assigned a task!",
+ "hCaptcha": "hCaptcha",
+ "successfullyAssignedJob": "Successfully claimed the task!",
"errorFetchingData": "There was an error while fetching data, please try again",
- "availableJobs": "Available Tasks",
- "myJobs": "My Tasks",
+ "availableJobs": "Available Jobs",
+ "myJobs": "My Jobs",
"jobsDiscovery": "Jobs Discovery",
"jobDescription": "Job description",
+ "action": "Action",
+ "taskDescription": "Task Desc.",
+ "taskDescriptionTitle": "Task Description",
+ "oracles": "Oracles",
+ "address": "Address",
"escrowAddress": "Escrow address",
"network": "Network",
+ "reward": "Reward",
"rewardAmount": "Reward amount",
"jobType": "Task type",
"expiresAt": "Expires at",
+ "expiryTime": "Expiry Time",
+ "expired": "Expired",
"refresh": "Refresh",
"status": "Status",
"selectJob": "Select Task",
+ "claimTask": "Claim Task",
"searchEscrowAddress": "Search escrow address",
"resign": "Resign",
"solve": "Solve",
"filter": "Filter",
+ "jobsFilter": "Jobs Filter",
+ "applyFilters": "Apply Filters",
"escrowAddressColumnId": "escrowAddress",
+ "statusFilter": {
+ "all": "All",
+ "inProgress": "In Progress",
+ "completed": "Completed"
+ },
"Ethereum": "Ethereum",
"Polygon": "Polygon",
"sortDirection": {
@@ -306,11 +346,15 @@
"fortune": "Fortune"
}
},
- "next": "Next"
+ "next": "Next",
+ "jobsFor": "Jobs for"
},
"hcaptchaLabeling": {
"description": "HMT payouts are automatically computed by an oracle based on correctness and volume of tasks completed. Payout timing depends on total job size, and can take as little as an hour or as long as a week or so, depending on overall job status.",
- "noJobs": "No jobs available at the moment. Please come back in:"
+ "noJobs": "You have reached the daily limit for the hCaptcha tasks, please come back later.",
+ "waitFor": "Wait for",
+ "browseAvailableJobs": "Browse Available Jobs",
+ "or": "Or"
},
"enableHCaptchaLabeling": {
"description": "CAPTCHA tasks involve the correct labeling of images in a group of images. Read the instructional prompt, and select the images that best correspond to that prompt.",
diff --git a/packages/apps/human-app/frontend/src/shared/styles/color-palette.ts b/packages/apps/human-app/frontend/src/shared/styles/color-palette.ts
index 7be38b2795..1cf299c20b 100644
--- a/packages/apps/human-app/frontend/src/shared/styles/color-palette.ts
+++ b/packages/apps/human-app/frontend/src/shared/styles/color-palette.ts
@@ -5,6 +5,7 @@ export const colorPalette = {
text: {
primary: '#320A8D',
secondary: '#B2AFC1',
+ light: '#6309ff',
disabled: '#CBCFE6',
disabledSecondary: '#8494C3',
auxiliary100: '#000000',
diff --git a/packages/apps/human-app/frontend/src/shared/styles/dark-color-palette.ts b/packages/apps/human-app/frontend/src/shared/styles/dark-color-palette.ts
index 459763d40f..0b13c7d654 100644
--- a/packages/apps/human-app/frontend/src/shared/styles/dark-color-palette.ts
+++ b/packages/apps/human-app/frontend/src/shared/styles/dark-color-palette.ts
@@ -7,6 +7,7 @@ export const darkColorPalette = {
text: {
primary: '#D4CFFF',
secondary: '#6C6978',
+ light: '#9387ff',
disabled: 'rgba(212, 207, 255, 0.5)',
disabledSecondary: 'rgba(147, 135, 255, 1)',
auxiliary100: '#ffffff',
@@ -72,11 +73,3 @@ export const darkColorPalette = {
// for 'warning', 'info' native colors from MUI were pointed as expected
// 'info' native colors from MUI were pointed as expected
} satisfies typeof colorPalette;
-
-// if Figma design was inconsistent for some reasons and there are extra colors for dark mode should be included in this object
-export const onlyDarkModeColor = {
- backArrowBg: 'rgba(246, 247, 254, 0.1)',
- additionalTextColor: '#9387FF',
- mainColorWithOpacity: '#CDC7FFCC',
- listItemColor: '#CDC7FF29',
-};
diff --git a/packages/apps/human-app/frontend/src/shared/styles/dark-theme.ts b/packages/apps/human-app/frontend/src/shared/styles/dark-theme.ts
index 2d8780982b..6bb3fb5298 100644
--- a/packages/apps/human-app/frontend/src/shared/styles/dark-theme.ts
+++ b/packages/apps/human-app/frontend/src/shared/styles/dark-theme.ts
@@ -1,8 +1,5 @@
import type { ThemeOptions } from '@mui/material';
-import {
- darkColorPalette,
- onlyDarkModeColor,
-} from '@/shared/styles/dark-color-palette';
+import { darkColorPalette } from '@/shared/styles/dark-color-palette';
import { typography } from '@/shared/styles/typography';
import { breakpoints } from '@/shared/styles/breakpoints';
@@ -153,7 +150,7 @@ export const darkTheme: ThemeOptions = {
MuiInputBase: {
styleOverrides: {
root: {
- borderColor: onlyDarkModeColor.mainColorWithOpacity,
+ borderColor: '#cdc7ffcc',
'&:hover': {
borderColor: 'white',
},
diff --git a/packages/apps/human-app/frontend/src/shared/styles/theme.ts b/packages/apps/human-app/frontend/src/shared/styles/theme.ts
index d8a130d7c5..36cf5f3331 100644
--- a/packages/apps/human-app/frontend/src/shared/styles/theme.ts
+++ b/packages/apps/human-app/frontend/src/shared/styles/theme.ts
@@ -8,6 +8,12 @@ declare module '@mui/material/Button' {
}
}
+declare module '@mui/material/Radio' {
+ interface RadioPropsColorOverrides {
+ accent: true;
+ }
+}
+
export const theme: ThemeOptions = {
typography,
components: {