diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/content-copy.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/content-copy.svg
deleted file mode 100644
index c4c4f89b16..0000000000
--- a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/content-copy.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/packages/apps/human-app/frontend/src/assets/icons-dark-mode/hcaptcha-disabled-icon.svg b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/hcaptcha-disabled-icon.svg
new file mode 100644
index 0000000000..c4d02582e3
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/assets/icons-dark-mode/hcaptcha-disabled-icon.svg
@@ -0,0 +1,9 @@
+
diff --git a/packages/apps/human-app/frontend/src/assets/icons/content-copy.svg b/packages/apps/human-app/frontend/src/assets/icons/content-copy.svg
deleted file mode 100644
index 4423c76961..0000000000
--- a/packages/apps/human-app/frontend/src/assets/icons/content-copy.svg
+++ /dev/null
@@ -1,3 +0,0 @@
-
diff --git a/packages/apps/human-app/frontend/src/assets/icons/hcaptcha-disabled-icon.svg b/packages/apps/human-app/frontend/src/assets/icons/hcaptcha-disabled-icon.svg
new file mode 100644
index 0000000000..4307dd8758
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/assets/icons/hcaptcha-disabled-icon.svg
@@ -0,0 +1,9 @@
+
diff --git a/packages/apps/human-app/frontend/src/assets/icons/hcaptcha.svg b/packages/apps/human-app/frontend/src/assets/icons/hcaptcha.svg
new file mode 100644
index 0000000000..65989c188c
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/assets/icons/hcaptcha.svg
@@ -0,0 +1,46 @@
+
diff --git a/packages/apps/human-app/frontend/src/assets/icons/triangle.svg b/packages/apps/human-app/frontend/src/assets/icons/triangle.svg
new file mode 100644
index 0000000000..917cd60674
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/assets/icons/triangle.svg
@@ -0,0 +1,3 @@
+
diff --git a/packages/apps/human-app/frontend/src/modules/signup/operator/views/add-stake.page.tsx b/packages/apps/human-app/frontend/src/modules/signup/operator/views/add-stake.page.tsx
index 9f3bb5c5b0..cdf8fd8a55 100644
--- a/packages/apps/human-app/frontend/src/modules/signup/operator/views/add-stake.page.tsx
+++ b/packages/apps/human-app/frontend/src/modules/signup/operator/views/add-stake.page.tsx
@@ -10,7 +10,6 @@ import {
import { getErrorMessageForError } from '@/shared/errors';
import { Alert } from '@/shared/components/ui/alert';
import { useColorMode } from '@/shared/contexts/color-mode';
-import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette';
import {
useAddStakeMutationState,
useGetStakedAmount,
@@ -20,7 +19,7 @@ import { StakeForm, Buttons } from '../components/add-stake';
import { stakedAmountFormatter } from '../utils';
export function AddStakeOperatorPage() {
- const { colorPalette, isDarkMode } = useColorMode();
+ const { colorPalette } = useColorMode();
const [displayForm, setDisplayForm] = useState(false);
const {
data: stakedAmount,
@@ -87,14 +86,7 @@ export function AddStakeOperatorPage() {
{t('operator.addStake.label')}
-
+
{stakedAmountFormatter(stakedAmount)}
{displayForm ? (
diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/breadcrumbs.tsx b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/breadcrumbs.tsx
new file mode 100644
index 0000000000..15ca2053e8
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/breadcrumbs.tsx
@@ -0,0 +1,42 @@
+import { Box, Link, Typography } from '@mui/material';
+import { Link as RouterLink } from 'react-router-dom';
+import { t } from 'i18next';
+
+import { routerPaths } from '@/router/router-paths';
+import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode';
+
+export function Breadcrumbs() {
+ const { colorPalette } = useColorMode();
+
+ return (
+
+
+ {t('worker.jobs.availableJobs')}
+
+
+ {'>'}
+
+
+ {t('worker.jobs.hCaptcha')}
+
+
+ );
+}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-accordion.tsx b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-accordion.tsx
index 082157d655..f198e6d185 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-accordion.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-accordion.tsx
@@ -1,11 +1,14 @@
-import Accordion from '@mui/material/Accordion';
-import AccordionSummary from '@mui/material/AccordionSummary';
-import AccordionDetails from '@mui/material/AccordionDetails';
-import Typography from '@mui/material/Typography';
-import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
-import Grid from '@mui/material/Grid';
-import { useEffect } from 'react';
+import { useEffect, useState } from 'react';
import { t } from 'i18next';
+import {
+ Accordion,
+ AccordionSummary,
+ AccordionDetails,
+ Grid,
+ Typography,
+} from '@mui/material';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+
import {
TopNotificationType,
useNotification,
@@ -16,10 +19,14 @@ import { useHCaptchaUserStats } from '../hooks';
import { UserStatsDetails } from './user-stats-details';
import { LoadingOverlay } from './user-stats-loading-overlay';
-const accordionWidth = { width: '284px' };
+const ACCORDION_WIDTH = '284px';
+const ACCORDION_HEIGHT = '48px';
export function UserStatsAccordion() {
+ const [isExpanded, setIsExpanded] = useState(false);
const { colorPalette } = useColorMode();
+ const { showNotification } = useNotification();
+
const {
data: hcaptchaUserStats,
isPending: isHcaptchaUserStatsPending,
@@ -28,28 +35,32 @@ export function UserStatsAccordion() {
refetch: refetchUserStats,
isRefetching: isHcaptchaUserStatsRefetching,
} = useHCaptchaUserStats();
- const { showNotification } = useNotification();
useEffect(() => {
if (isHcaptchaUserStatsError) {
showNotification({
- type: TopNotificationType.WARNING,
+ type: TopNotificationType.ERROR,
message: getErrorMessageForError(hcaptchaUserStatsError),
});
}
- // eslint-disable-next-line react-hooks/exhaustive-deps -- ...
- }, [isHcaptchaUserStatsError, hcaptchaUserStatsError]);
+ }, [isHcaptchaUserStatsError, hcaptchaUserStatsError, showNotification]);
return (
-
+
{
+ setIsExpanded(expanded);
+ }}
sx={{
- ...accordionWidth,
position: 'relative',
overflow: 'hidden',
+ width: ACCORDION_WIDTH,
+ minHeight: ACCORDION_HEIGHT,
+ zIndex: 1,
}}
>
- {isHcaptchaUserStatsRefetching && (
+ {isExpanded && isHcaptchaUserStatsRefetching && (
}
id="panel1-header"
- sx={{ ...accordionWidth, height: '76px' }}
+ sx={{
+ width: ACCORDION_WIDTH,
+ height: ACCORDION_HEIGHT,
+ py: 1.5,
+ '& > .MuiAccordionSummary-content': {
+ m: 0,
+ },
+ '&.Mui-expanded': { minHeight: ACCORDION_HEIGHT },
+ }}
>
{t('worker.hcaptchaLabelingStats.statistics')}
{hcaptchaUserStats ? (
-
+
void refetchUserStats()}
stats={hcaptchaUserStats}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-details.tsx b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-details.tsx
index 6196dfd4be..4bb9a0a58e 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-details.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-details.tsx
@@ -1,33 +1,29 @@
import { Divider, IconButton, Stack, Typography } from '@mui/material';
import { t } from 'i18next';
+
import { RefreshIcon } from '@/shared/components/ui/icons';
-import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette';
import { useColorMode } from '@/shared/contexts/color-mode';
import { useIsMobile } from '@/shared/hooks';
import { type HCaptchaUserStatsSuccess } from '../types';
-export function UserStatsDetails({
- stats,
- refetch,
- isRefetching,
-}: Readonly<{
+type Props = {
stats: HCaptchaUserStatsSuccess;
refetch: () => void;
isRefetching: boolean;
-}>) {
- const { colorPalette, isDarkMode } = useColorMode();
- const isMobile = useIsMobile();
+};
- const statsColor = isDarkMode
- ? onlyDarkModeColor.additionalTextColor
- : colorPalette.primary.light;
+export function UserStatsDetails({ stats, refetch, isRefetching }: Props) {
+ const { colorPalette } = useColorMode();
+ const isMobile = useIsMobile();
return (
-
-
-
-
+ {!isMobile && (
+
+ )}
+
+
+
{t('worker.hcaptchaLabelingStats.allTime')}
{t('worker.hcaptchaLabelingStats.jobsServed')}
-
+
{stats.served}
@@ -46,7 +42,7 @@ export function UserStatsDetails({
{t('worker.hcaptchaLabelingStats.jobsComplete')}
-
+
{stats.solved}
@@ -54,29 +50,41 @@ export function UserStatsDetails({
{t('worker.hcaptchaLabelingStats.hmtEarned')}
-
+
{stats.balance.total}{' '}
-
+
{t('inputMasks.humanCurrencySuffix')}
-
+
-
+
-
+
{t('worker.hcaptchaLabelingStats.lastHour')}
{t('worker.hcaptchaLabelingStats.earnedLastHour')}
-
+
{stats.balance.recent}{' '}
-
+
{t('inputMasks.humanCurrencySuffix')}
-
+
diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-drawer.tsx b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-drawer.tsx
index db65ccd070..dcd304ab0e 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-drawer.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/components/user-stats-drawer.tsx
@@ -1,80 +1,98 @@
-import { Box, CssBaseline, Drawer, Stack, Typography } from '@mui/material';
+import { Box, Drawer, IconButton, Stack, Typography } from '@mui/material';
+import CloseIcon from '@mui/icons-material/Close';
import { t } from 'i18next';
+
import { Loader } from '@/shared/components/ui/loader';
import { Alert } from '@/shared/components/ui/alert';
import { getErrorMessageForError } from '@/shared/errors';
import { useHCaptchaUserStats } from '../hooks';
import { UserStatsDetails } from './user-stats-details';
import { LoadingOverlay } from './user-stats-loading-overlay';
+import { useColorMode } from '@/shared/contexts/color-mode';
-export interface UserStatsDrawerNavigationProps {
+type Props = {
isOpen: boolean;
-}
+ onClose: () => void;
+};
+
+export function UserStatsDrawer({ isOpen, onClose }: Props) {
+ const { colorPalette } = useColorMode();
-export function UserStatsDrawer({
- isOpen,
-}: Readonly) {
const {
data: hcaptchaUserStats,
error: hcaptchaUserStatsError,
- status: hcaptchaUserStatsStatus,
+ isSuccess,
+ isPending,
refetch: hcaptchaUserStatsRefetch,
isRefetching: isHcaptchaUserStatsRefetching,
} = useHCaptchaUserStats();
return (
-
-
- theme.zIndex.drawer,
+ '& .MuiDrawer-paper': {
width: '100%',
- flexShrink: 0,
- '& .MuiDrawer-paper': {
- width: '100%',
- paddingTop: 11,
- },
- }}
- >
-
- {isHcaptchaUserStatsRefetching && (
-
+
+ {isHcaptchaUserStatsRefetching && (
+
+ )}
+
+
+ {t('worker.hcaptchaLabelingStats.hCapchaStatistics')}
+
+
+
+
+
+
+ {isSuccess && (
+ void hcaptchaUserStatsRefetch()}
+ stats={hcaptchaUserStats}
+ isRefetching={isHcaptchaUserStatsRefetching}
/>
)}
-
- {hcaptchaUserStatsStatus === 'success' ? (
- <>
-
- {t('worker.hcaptchaLabelingStats.hCapchaStatistics')}
-
- void hcaptchaUserStatsRefetch()}
- stats={hcaptchaUserStats}
- isRefetching={isHcaptchaUserStatsRefetching}
- />
- >
- ) : null}
- {hcaptchaUserStatsStatus === 'error' ? (
-
- {getErrorMessageForError(hcaptchaUserStatsError)}
-
- ) : null}
- {hcaptchaUserStatsStatus === 'pending' ? (
-
- ) : null}
-
-
-
-
+ {!!hcaptchaUserStatsError && (
+
+ {getErrorMessageForError(hcaptchaUserStatsError)}
+
+ )}
+ {isPending && }
+
+
+
);
}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/enable-labeler.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/enable-labeler.page.tsx
index 71b64fa101..b8315d7aa4 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/enable-labeler.page.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/enable-labeler.page.tsx
@@ -1,81 +1,100 @@
-import Grid from '@mui/material/Grid';
-import Paper from '@mui/material/Paper';
+import { useEffect } from 'react';
import { t } from 'i18next';
-import Typography from '@mui/material/Typography';
-import { Navigate } from 'react-router-dom';
+import { Box, Paper, Stack, Typography } from '@mui/material';
+
import { Button } from '@/shared/components/ui/button';
-import { PageCardError } from '@/shared/components/ui/page-card';
import { getErrorMessageForError } from '@/shared/errors';
-import { breakpoints } from '@/shared/styles/breakpoints';
-import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user';
-import { routerPaths } from '@/router/router-paths';
+import { useColorMode } from '@/shared/contexts/color-mode/use-color-mode';
+import {
+ TopNotificationType,
+ useNotification,
+} from '@/shared/hooks/use-notification';
import { useEnableHCaptchaLabelingMutation } from './hooks';
+import { Breadcrumbs } from './components/breadcrumbs';
export function EnableLabelerPage() {
- const { user } = useAuthenticatedUser();
- const { mutate, error, isError, isPending } =
+ const { colorPalette } = useColorMode();
+ const { showNotification } = useNotification();
+ const { mutate, error, isError, isPending, reset } =
useEnableHCaptchaLabelingMutation();
- if (!user.wallet_address) {
- return ;
- }
-
- if (isError) {
- return ;
- }
+ useEffect(() => {
+ if (isError) {
+ showNotification({
+ message: getErrorMessageForError(error),
+ type: TopNotificationType.ERROR,
+ });
+ reset();
+ }
+ }, [error, isError, showNotification, reset]);
return (
-
-
+
+
+
-
-
-
+
{t('worker.enableHCaptchaLabeling.description')}
-
-
-
-
-
+
+
+
+
);
}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hcaptcha-labeling.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hcaptcha-labeling.page.tsx
index 8ff36d9b51..5429d3c0ff 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hcaptcha-labeling.page.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hcaptcha-labeling.page.tsx
@@ -1,17 +1,16 @@
-import HCaptcha from '@hcaptcha/react-hcaptcha';
-import { Grid, Paper, Typography } from '@mui/material';
+import { useRef, useState } from 'react';
import { t } from 'i18next';
-import { useNavigate } from 'react-router-dom';
-import { useRef } from 'react';
+import { useNavigate, Link as RouterLink } from 'react-router-dom';
+import HCaptcha from '@hcaptcha/react-hcaptcha';
+import { Box, Divider, Paper, Stack, Typography } from '@mui/material';
+
import { env } from '@/shared/env';
-import { breakpoints } from '@/shared/styles/breakpoints';
import { Counter } from '@/shared/components/ui/counter';
import { getErrorMessageForError } from '@/shared/errors';
import { getTomorrowDate } from '@/shared/helpers/date';
import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user';
import { useHCaptchaLabelingNotifications } from '@/modules/worker/hooks/use-hcaptcha-labeling-notifications';
import { useColorMode } from '@/shared/contexts/color-mode';
-import { onlyDarkModeColor } from '@/shared/styles/dark-color-palette';
import {
PageCardLoader,
PageCardError,
@@ -21,21 +20,23 @@ import {
useDailyHmtSpent,
useSolveHCaptchaMutation,
} from './hooks';
+import { Breadcrumbs } from './components/breadcrumbs';
+import { Button } from '@/shared/components/ui/button';
+import { routerPaths } from '@/router/router-paths';
+import { UserStatsAccordion } from './components/user-stats-accordion';
+import { useIsMobile } from '@/shared/hooks/use-is-mobile';
+import { UserStatsDrawer } from './components/user-stats-drawer';
export function HcaptchaLabelingPage() {
- const { colorPalette, isDarkMode } = useColorMode();
+ const [isStatsDrawerOpen, setIsStatsDrawerOpen] = useState(false);
const captchaRef = useRef(null);
+
+ const { colorPalette } = useColorMode();
const { user } = useAuthenticatedUser();
- const { onSuccess, onError } = useHCaptchaLabelingNotifications();
- const statsColor = isDarkMode
- ? onlyDarkModeColor.additionalTextColor
- : colorPalette.primary.light;
+ const navigate = useNavigate();
+ const isMobile = useIsMobile();
- const resetCaptcha = () => {
- if (captchaRef.current) {
- captchaRef.current.resetCaptcha();
- }
- };
+ const { onSuccess, onError } = useHCaptchaLabelingNotifications();
const { mutate: solveHCaptchaMutation } = useSolveHCaptchaMutation({
onSuccess: () => {
@@ -62,7 +63,8 @@ export function HcaptchaLabelingPage() {
error: dailyHmtSpentError,
} = useDailyHmtSpent();
- const navigate = useNavigate();
+ const isError = isDailyHmtSpentError || isHcaptchaUserStatsError;
+ const isPending = isHcaptchaUserStatsPending || isDailyHmtSpentPending;
const canSolveCaptcha =
dailyHmtSpent &&
@@ -75,90 +77,189 @@ export function HcaptchaLabelingPage() {
solveHCaptchaMutation({ token });
};
- if (isHcaptchaUserStatsPending || isDailyHmtSpentPending) {
- return ;
- }
-
- if (isHcaptchaUserStatsError || isDailyHmtSpentError) {
- return (
-
- );
- }
+ const resetCaptcha = () => {
+ if (captchaRef.current) {
+ captchaRef.current.resetCaptcha();
+ }
+ };
return (
-
-
-
+ {isMobile ? (
+
+ ) : (
+
+ )}
+
+
+
-
-
- {t('worker.hcaptchaLabeling.description')}
-
- {canSolveCaptcha ? (
-
-
-
- ) : (
-
-
- {t('worker.hcaptchaLabeling.noJobs')}
-
-
- {
- navigate(0);
+ {isPending && !isError && }
+ {isError && (
+
+ )}
+ {!isPending && !isError && (
+
+ {canSolveCaptcha ? (
+ <>
+
+ {t('worker.hcaptchaLabeling.description')}
+
+
+
+
+ >
+ ) : (
+
+
+ {t('worker.hcaptchaLabeling.noJobs')}
+
+
-
-
- )}
-
-
-
-
+ >
+
+
+ {t('worker.hcaptchaLabeling.waitFor')}
+
+
+ {
+ navigate(0);
+ }}
+ />
+
+
+
+
+
+ {t('worker.hcaptchaLabeling.or')}
+
+
+
+
+
+
+
+ )}
+
+ )}
+
+
+ setIsStatsDrawerOpen(false)}
+ />
+
);
}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/enable-hcaptcha-labeling.ts b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/enable-hcaptcha-labeling.ts
index fcc7c9ccdd..52140a2f4f 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/enable-hcaptcha-labeling.ts
+++ b/packages/apps/human-app/frontend/src/modules/worker/hcaptcha-labeling/hooks/enable-hcaptcha-labeling.ts
@@ -1,5 +1,6 @@
import { useMutation } from '@tanstack/react-query';
import { useNavigate } from 'react-router-dom';
+
import { routerPaths } from '@/router/router-paths';
import { useAccessTokenRefresh } from '@/api/hooks/use-access-token-refresh';
import * as hCaptchaLabelingService from '../services/hcaptcha-labeling.service';
@@ -7,12 +8,11 @@ import * as hCaptchaLabelingService from '../services/hcaptcha-labeling.service'
export function useEnableHCaptchaLabelingMutation() {
const navigate = useNavigate();
const { refreshAccessTokenAsync } = useAccessTokenRefresh();
+
const mutation = useMutation({
mutationFn: async () => {
const result = await hCaptchaLabelingService.enableHCaptchaLabeling();
-
await refreshAccessTokenAsync({ authType: 'web2' });
-
return result;
},
onSuccess: () => {
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/hcaptha-widget.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/hcaptha-widget.tsx
new file mode 100644
index 0000000000..985b8a9576
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/hcaptha-widget.tsx
@@ -0,0 +1,269 @@
+import { useMemo } from 'react';
+import { Box, Stack, Typography } from '@mui/material';
+import { Link, useNavigate } from 'react-router-dom';
+import { t } from 'i18next';
+
+import { env } from '@/shared/env';
+import { Button } from '@/shared/components/ui/button';
+import {
+ HcaptchaDisabledIcon,
+ HcaptchaIcon,
+} from '@/shared/components/ui/icons';
+import { useColorMode } from '@/shared/contexts/color-mode';
+import { routerPaths } from '@/router/router-paths';
+import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user';
+import { useIsMobile } from '@/shared/hooks/use-is-mobile';
+import {
+ useHCaptchaUserStats,
+ useDailyHmtSpent,
+} from '../../hcaptcha-labeling/hooks';
+import { getTomorrowDate } from '@/shared/helpers/date';
+import { Counter } from '@/shared/components/ui/counter';
+import { Loader } from '@/shared/components/ui/loader';
+
+const getHCaptchaPagePath = (siteKey: string | null | undefined): string =>
+ siteKey
+ ? routerPaths.worker.HcaptchaLabeling
+ : routerPaths.worker.enableLabeler;
+
+const renderDescription = (color: string, isMobile: boolean) => {
+ let description = '';
+
+ if (isMobile) {
+ description = t('worker.hcaptchaWidget.shortDescription');
+ } else {
+ description = t('worker.hcaptchaWidget.description');
+ }
+
+ return (
+
+ {description}
+
+ );
+};
+
+export function HCaptchaWidget() {
+ const { colorPalette } = useColorMode();
+ const { user } = useAuthenticatedUser();
+ const isMobile = useIsMobile();
+ const navigate = useNavigate();
+
+ const { data: hcaptchaUserStats, isPending: isHcaptchaUserStatsPending } =
+ useHCaptchaUserStats();
+
+ const { data: dailyHmtSpent, isPending: isDailyHmtSpentPending } =
+ useDailyHmtSpent();
+
+ const hCaptchaPagePath = useMemo(
+ () => getHCaptchaPagePath(user.site_key),
+ [user.site_key]
+ );
+
+ if (isHcaptchaUserStatsPending || isDailyHmtSpentPending) {
+ return (
+
+
+
+ );
+ }
+
+ const isCaptchaLimitReached =
+ !!dailyHmtSpent &&
+ !!hcaptchaUserStats &&
+ hcaptchaUserStats.currentDateStats.solved >=
+ env.VITE_DAILY_SOLVED_CAPTCHA_LIMIT &&
+ dailyHmtSpent.spend >= env.VITE_HMT_DAILY_SPENT_LIMIT;
+
+ return (
+
+ {isCaptchaLimitReached ? (
+ <>
+
+
+
+
+ {t('worker.hcaptchaWidget.titleDisabled')}
+
+ {!isMobile && (
+
+ {t('worker.hcaptchaWidget.descriptionDisabled')}
+
+ )}
+
+
+
+
+ {isMobile
+ ? t('worker.hcaptchaWidget.nextIn')
+ : t('worker.hcaptchaWidget.nextAvailable')}
+
+
+ {
+ navigate(0);
+ }}
+ />
+
+
+ >
+ ) : (
+ <>
+
+
+
+
+ {t('worker.hcaptchaWidget.title')}
+
+ {!isMobile &&
+ renderDescription(colorPalette.text.auxiliary200, isMobile)}
+
+
+
+ {isMobile &&
+ renderDescription(colorPalette.text.auxiliary200, isMobile)}
+
+
+ >
+ )}
+
+ );
+}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/index.ts
index 9bc965d5af..e8eb2de22d 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/index.ts
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/index.ts
@@ -1,4 +1,4 @@
export * from './oracles-table-job-types-select';
-export * from './oracles-table';
+export * from './oracles-list';
export * from './oracles-table-item-mobile';
export * from './oracles-table-desktop';
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracle-job-card.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracle-job-card.tsx
new file mode 100644
index 0000000000..3cb2cd4af5
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracle-job-card.tsx
@@ -0,0 +1,283 @@
+import { useState } from 'react';
+import {
+ Box,
+ Card,
+ CardContent,
+ Collapse,
+ Stack,
+ Tooltip,
+ Typography,
+} from '@mui/material';
+import { t } from 'i18next';
+import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
+import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
+
+import { Oracle } from '../../services/oracles.service';
+import {
+ MenuIcon,
+ OracleAddressIcon,
+ OracleRewardIcon,
+} from '@/shared/components/ui/icons';
+import { useColorMode } from '@/shared/contexts/color-mode';
+import { shortenEscrowAddress } from '@/shared/helpers/evm';
+import { CopyToClipboardButton } from '@/shared/components/ui/copy-to-clipboard-button';
+import { useIsMobile } from '@/shared/hooks/use-is-mobile';
+import { Button } from '@/shared/components/ui/button';
+import { JOB_TYPES } from '@/shared/consts';
+import { JobType } from '@/modules/smart-contracts/EthKVStore/config';
+import { Chip } from '@/shared/components/ui/chip';
+
+function JobTypesTooltipTitle({ jobTypes }: { jobTypes: string[] }) {
+ return (
+
+ {jobTypes.map((jobType) => {
+ const element = JOB_TYPES.find((j) => j === jobType) as JobType;
+ const label = t(`jobTypeLabels.${element}`);
+ return (
+
+ {label}
+
+ );
+ })}
+
+ );
+}
+
+export function OracleJobCard({ oracle }: { oracle: Oracle }) {
+ const [isTaskTypesOpen, setIsTaskTypesOpen] = useState(false);
+
+ const { colorPalette } = useColorMode();
+ const isMobile = useIsMobile();
+
+ const isRewardAmountsEqual =
+ oracle.minRewardAmount === oracle.maxRewardAmount;
+ const hasTasks = oracle.nTasks > 0;
+
+ return (
+
+
+
+ {oracle.name}
+
+
+
+
+
+ {t('worker.oraclesList.address')}:
+
+
+ {shortenEscrowAddress(oracle.address, 4, 4)}
+
+
+
+
+
+
+ {t('worker.oraclesList.reward')}:
+
+
+ {isRewardAmountsEqual ? (
+ <>
+ {oracle.minRewardAmount} {oracle.rewardToken}
+ >
+ ) : (
+ <>
+ {oracle.minRewardAmount} {oracle.rewardToken} -{' '}
+ {oracle.maxRewardAmount} {oracle.rewardToken}
+ >
+ )}
+
+
+
+
+
+ {isMobile ? (
+
+ }
+ sx={{
+ p: 0,
+ bgcolor: 'transparent',
+ color: colorPalette.text.auxiliary200,
+ }}
+ onClick={() => hasTasks && setIsTaskTypesOpen(!isTaskTypesOpen)}
+ >
+ {oracle.nTasks}{' '}
+ {oracle.nTasks === 1
+ ? t('worker.oraclesList.task')
+ : t('worker.oraclesList.tasks')}
+
+ ) : (
+ }
+ disableHoverListener={isMobile || !hasTasks}
+ slotProps={{
+ tooltip: {
+ sx: {
+ p: 1.5,
+ bgcolor: colorPalette.background.paper,
+ boxShadow: 'none',
+ borderRadius: '15px',
+ border: `1px solid ${colorPalette.border.main}`,
+ },
+ },
+ }}
+ >
+
+
+ {oracle.nTasks}{' '}
+ {oracle.nTasks === 1
+ ? t('worker.oraclesList.task')
+ : t('worker.oraclesList.tasks')}
+
+
+ )}
+
+
+
+
+ {oracle.jobTypes.map((jobType) => {
+ const element = JOB_TYPES.find((j) => j === jobType) as JobType;
+ const label = t(`jobTypeLabels.${element}`);
+ return (
+
+ );
+ })}
+
+
+
+
+
+ );
+}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-list.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-list.tsx
new file mode 100644
index 0000000000..2713b8b605
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-list.tsx
@@ -0,0 +1,57 @@
+import { Grid, Stack } from '@mui/material';
+import { useTranslation } from 'react-i18next';
+
+import { Loader } from '@/shared/components/ui/loader';
+import { NoRecords } from '@/shared/components/ui/no-records';
+import { PageCardError } from '@/shared/components/ui/page-card';
+import { OracleJobCard } from './oracle-job-card';
+import { Oracle } from '../../services/oracles.service';
+
+type OraclesListProps = {
+ data: Oracle[] | undefined;
+ isError: boolean;
+ isPending: boolean;
+};
+
+export function OraclesList({ data, isError, isPending }: OraclesListProps) {
+ const { t } = useTranslation();
+
+ if (isPending) {
+ return (
+
+
+
+ );
+ }
+
+ if (isError) {
+ return (
+
+ );
+ }
+
+ if (!data?.length) {
+ return ;
+ }
+
+ return (
+
+ {data.map((oracle) => (
+
+
+
+ ))}
+
+ );
+}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table.tsx
deleted file mode 100644
index 9e19e8af0e..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/components/oracles-table.tsx
+++ /dev/null
@@ -1,69 +0,0 @@
-import { Stack, Grid } from '@mui/material';
-import { useEffect } from 'react';
-import { useTranslation } from 'react-i18next';
-import { useIsMobile } from '@/shared/hooks/use-is-mobile';
-import { Loader } from '@/shared/components/ui/loader';
-import { NoRecords } from '@/shared/components/ui/no-records';
-import { useGetOraclesNotifications } from '@/modules/worker/hooks/use-get-oracles-notifications';
-import { PageCardError } from '@/shared/components/ui/page-card';
-import { useGetOracles } from '../../hooks';
-import { OraclesTableItemMobile } from './oracles-table-item-mobile';
-import { OraclesTableDesktop } from './oracles-table-desktop';
-
-export function OraclesTable() {
- const isMobile = useIsMobile();
- const { t } = useTranslation();
- const { onError } = useGetOraclesNotifications();
- const {
- data: oraclesData,
- isError: isOraclesDataError,
- isPending: isOraclesDataPending,
- error: oraclesDataError,
- } = useGetOracles();
-
- useEffect(() => {
- if (oraclesDataError) {
- onError(oraclesDataError);
- }
- }, [oraclesDataError, onError]);
-
- if (isOraclesDataPending) {
- return (
-
-
-
- );
- }
-
- if (isOraclesDataError) {
- return (
-
- );
- }
-
- if (!oraclesData.length) {
- return ;
- }
-
- return isMobile ? (
-
- {oraclesData.map((oracle) => (
-
- ))}
-
- ) : (
-
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/index.ts
index 7db6869470..c7ea564552 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/index.ts
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/index.ts
@@ -1,2 +1 @@
-export * from './is-hcaptcha-oracle';
export * from './should-navigate-to-registration';
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.spec.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.spec.ts
deleted file mode 100644
index 117e9daaf9..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.spec.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { describe, expect, it, vi } from 'vitest';
-import { faker } from '@faker-js/faker';
-import { env } from '@/shared/env';
-import { isHCaptchaOracle } from './is-hcaptcha-oracle';
-
-vi.mock('@/shared/env', () => ({
- env: {
- VITE_H_CAPTCHA_ORACLE_ADDRESS: '0x1234567890abcdef1234567890abcdef12345678',
- },
-}));
-
-describe('isHCaptchaOracle Helper', () => {
- it('should return true when the address matches the hCaptcha oracle address', () => {
- const result = isHCaptchaOracle(
- '0x1234567890abcdef1234567890abcdef12345678'
- );
- expect(result).toBe(true);
- });
-
- it('should return false when the address does not match the hCaptcha oracle address', () => {
- const result = isHCaptchaOracle(faker.finance.ethereumAddress());
- expect(result).toBe(false);
- });
-
- it('should handle case sensitivity correctly', () => {
- const mixedCaseAddress = env.VITE_H_CAPTCHA_ORACLE_ADDRESS.replace(
- 'abcdef',
- 'ABCDEF'
- );
-
- const result = isHCaptchaOracle(mixedCaseAddress);
- expect(result).toBe(true);
- });
-
- it('should handle empty strings', () => {
- const result = isHCaptchaOracle('');
- expect(result).toBe(false);
- });
-});
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.ts
deleted file mode 100644
index 9b5cefc8a3..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/is-hcaptcha-oracle.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-import { env } from '@/shared/env';
-
-export const isHCaptchaOracle = (address: string): boolean =>
- address.toLowerCase() === env.VITE_H_CAPTCHA_ORACLE_ADDRESS.toLowerCase();
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.spec.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.spec.ts
index 133385f694..a1eb43e1e1 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.spec.ts
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/helpers/should-navigate-to-registration.spec.ts
@@ -12,6 +12,10 @@ describe('shouldNavigateToRegistration Helper', () => {
url: faker.internet.url(),
jobTypes: [faker.word.noun()],
name: faker.company.name(),
+ nTasks: faker.number.int({ min: 1, max: 10 }),
+ minRewardAmount: faker.finance.amount(),
+ maxRewardAmount: faker.finance.amount(),
+ rewardToken: faker.finance.currencyCode(),
};
const oracleWithNoRegistration: Oracle = {
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-select-oracle-navigation.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-select-oracle-navigation.ts
index 1c81650c57..9879955be2 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-select-oracle-navigation.ts
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/hooks/use-select-oracle-navigation.ts
@@ -1,26 +1,15 @@
+import { useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
-import { useCallback, useMemo } from 'react';
-import { useAuthenticatedUser } from '@/modules/auth/hooks/use-authenticated-user';
+
import { routerPaths } from '@/router/router-paths';
-import { shouldNavigateToRegistration, isHCaptchaOracle } from '../helpers';
+import { shouldNavigateToRegistration } from '../helpers';
import { type Oracle } from '../../services/oracles.service';
import { useGetRegistrationDataInOracles } from './use-get-registration-data-oracles';
-const getHCaptchaPagePath = (siteKey: string | null | undefined): string =>
- siteKey
- ? routerPaths.worker.HcaptchaLabeling
- : routerPaths.worker.enableLabeler;
-
export const useSelectOracleNavigation = () => {
const navigate = useNavigate();
- const { user } = useAuthenticatedUser();
const { data } = useGetRegistrationDataInOracles();
- const hCaptchaPagePath = useMemo(
- () => getHCaptchaPagePath(user.site_key),
- [user.site_key]
- );
-
const selectOracle = useCallback(
(oracle: Oracle) => {
if (shouldNavigateToRegistration(oracle, data)) {
@@ -30,16 +19,11 @@ export const useSelectOracleNavigation = () => {
return;
}
- if (isHCaptchaOracle(oracle.address)) {
- navigate(hCaptchaPagePath);
- return;
- }
-
navigate(`${routerPaths.worker.jobs}/${oracle.address}`, {
state: { oracle },
});
},
- [data, navigate, hCaptchaPagePath]
+ [data, navigate]
);
return { selectOracle };
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/jobs-discovery.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/jobs-discovery.page.tsx
index 6fcb56287a..5280eb2f72 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/jobs-discovery.page.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs-discovery/jobs-discovery.page.tsx
@@ -1,25 +1,94 @@
-import { Paper, Stack } from '@mui/material';
+import { useEffect } from 'react';
+import { Box, Stack, Typography } from '@mui/material';
+import { t } from 'i18next';
+
+import { OraclesList } from './components/oracles-list';
+import { HCaptchaWidget } from './components/hcaptha-widget';
+import { useColorMode } from '@/shared/contexts/color-mode';
import { useIsMobile } from '@/shared/hooks/use-is-mobile';
-import { OraclesTableJobTypesSelect, OraclesTable } from './components';
+import { useGetOracles } from '../hooks/use-get-oracles';
+import { useGetOraclesNotifications } from '../hooks/use-get-oracles-notifications';
+
+const bull = (
+
+ •
+
+);
export function JobsDiscoveryPage() {
+ const { colorPalette } = useColorMode();
const isMobile = useIsMobile();
+ const { onError } = useGetOraclesNotifications();
+ const {
+ data: oraclesData,
+ isError,
+ isPending,
+ isSuccess,
+ error,
+ } = useGetOracles();
+
+ useEffect(() => {
+ if (error) {
+ onError(error);
+ }
+ }, [error, onError]);
+
+ const oraclesCount = oraclesData?.length ?? 0;
+
return (
-
-
+
-
-
-
+
+
+
+
+
+ {t('worker.oraclesList.jobOracles')}
+
+ {isSuccess && (
+ <>
+ {bull}
+
+ {oraclesCount}{' '}
+ {oraclesCount === 1
+ ? t('worker.oraclesList.source')
+ : t('worker.oraclesList.sources')}
+
+ >
+ )}
+
+
+
);
}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/available-jobs-view.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/available-jobs-view.tsx
deleted file mode 100644
index ac645716d3..0000000000
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/available-jobs/available-jobs-view.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import { useIsMobile } from '@/shared/hooks/use-is-mobile';
-import { AvailableJobsTableDesktop } from './components/desktop';
-import { AvailableJobsTableMobile } from './components/mobile';
-
-interface AvailableJobsTableView {
- chainIdsEnabled: number[];
-}
-
-export function AvailableJobsView({ chainIdsEnabled }: AvailableJobsTableView) {
- const isMobile = useIsMobile();
-
- return isMobile ? (
-
- ) : (
-
- );
-}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/evm-address.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/evm-address.tsx
index 1370dd854a..6d2e704c0c 100644
--- a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/evm-address.tsx
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/evm-address.tsx
@@ -1,7 +1,6 @@
-import Tooltip from '@mui/material/Tooltip';
-import Typography from '@mui/material/Typography';
+import { Tooltip, Typography } from '@mui/material';
+
import { shortenEscrowAddress } from '@/shared/helpers/evm';
-import { breakpoints } from '@/shared/styles/breakpoints';
import { useIsMobile } from '@/shared/hooks/use-is-mobile';
import { useColorMode } from '@/shared/contexts/color-mode';
@@ -9,19 +8,13 @@ export function EvmAddress({ address }: { address: string }) {
const { colorPalette } = useColorMode();
const isMobile = useIsMobile();
- const shortAddress = isMobile
- ? shortenEscrowAddress(address, 4, 4)
- : shortenEscrowAddress(address);
+ const shortAddress = shortenEscrowAddress(address, 4, 4);
return (
{shortAddress}
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/index.ts b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/index.ts
index ca383549de..edc417100b 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
@@ -6,3 +6,4 @@ export * from './reward-amount';
export * from './sorting';
export * from './more-button';
export * from './report-abuse-modal';
+export * from './network-name';
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..dd48dec93a 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;
diff --git a/packages/apps/human-app/frontend/src/modules/worker/jobs/components/network-name.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/network-name.tsx
new file mode 100644
index 0000000000..5db2e7c854
--- /dev/null
+++ b/packages/apps/human-app/frontend/src/modules/worker/jobs/components/network-name.tsx
@@ -0,0 +1,22 @@
+import { Typography } from '@mui/material';
+
+import { getNetworkName } from '@/modules/smart-contracts/get-network-name';
+import { useColorMode } from '@/shared/contexts/color-mode';
+import { useIsMobile } from '@/shared/hooks';
+
+export function NetworkName({ chainId }: { chainId: number }) {
+ const { colorPalette } = useColorMode();
+ const isMobile = useIsMobile();
+
+ return (
+
+ {getNetworkName(chainId)}
+
+ );
+}
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..534b4bdcd9 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,37 @@ 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/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/jobs.page.tsx b/packages/apps/human-app/frontend/src/modules/worker/jobs/jobs.page.tsx
index a4e79bd62c..4d6724549f 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
@@ -7,12 +7,9 @@ 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 +27,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) => {
@@ -115,22 +106,10 @@ export function JobsPage() {
- {isError ? (
-
- ) : (
-
- )}
+ {isError ? : null}
- {isError ? (
-
- ) : (
-
- )}
+ {isError ? : null}
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 5db121de0c..9d573f20e6 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 (
-
+
}
@@ -209,6 +175,9 @@ export function WorkerProfilePage() {
{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..944d4b927e 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,7 @@
import { z } from 'zod';
import { ApiClientError, authorizedHumanAppApiClient } from '@/api';
import { env } from '@/shared/env';
-import { MainnetChains, TestnetChains } from '@/modules/smart-contracts/chains';
+import { TestnetChains } from '@/modules/smart-contracts/chains';
const apiPaths = {
oracles: '/oracles',
@@ -13,6 +13,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,17 +30,76 @@ 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,
-};
+// 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,
+// };
+
+//TODO: Remove before merging
+const MOCKED_ORACLES: Oracle[] = [
+ {
+ address: '0x1111111111111111111111111111111111111111',
+ chainId: TestnetChains[0].chainId,
+ jobTypes: ['image_points'],
+ role: 'Recording Oracle',
+ url: 'https://example.com/oracle-1',
+ name: 'Mock Oracle 1',
+ nTasks: 1,
+ minRewardAmount: '0.001',
+ maxRewardAmount: '0.002',
+ rewardToken: 'HMT',
+ registrationNeeded: false,
+ },
+ {
+ address: '0x2222222222222222222222222222222222222222',
+ chainId: TestnetChains[0].chainId,
+ jobTypes: ['image_points', 'image_boxes'],
+ role: 'Recording Oracle',
+ url: 'https://example.com/oracle-2',
+ name: 'Mock Oracle 2',
+ nTasks: 2,
+ minRewardAmount: '0.1',
+ maxRewardAmount: '0.2',
+ rewardToken: 'HMT',
+ registrationNeeded: false,
+ },
+ {
+ address: '0x3333333333333333333333333333333333333333',
+ chainId: TestnetChains[0].chainId,
+ jobTypes: ['image_points', 'image_boxes'],
+ role: 'Recording Oracle',
+ url: 'https://example.com/oracle-3',
+ name: 'Mock Oracle 3',
+ nTasks: 3,
+ minRewardAmount: '0.1',
+ maxRewardAmount: '0.2',
+ rewardToken: 'HMT',
+ registrationNeeded: false,
+ },
+ {
+ address: '0x4444444444444444444444444444444444444444',
+ chainId: TestnetChains[0].chainId,
+ jobTypes: [
+ 'image_points',
+ 'image_boxes',
+ 'image_polygons',
+ 'social_media_engagement',
+ ],
+ role: 'Recording Oracle',
+ url: 'https://example.com/oracle-4',
+ name: 'Mock Oracle 4',
+ nTasks: 8,
+ minRewardAmount: '1',
+ maxRewardAmount: '2',
+ rewardToken: 'HMT',
+ registrationNeeded: false,
+ },
+];
async function getOracles(selectedJobTypes: string[]) {
try {
@@ -48,11 +111,8 @@ async function getOracles(selectedJobTypes: string[]) {
let oracles: Oracle[] = [];
- if (
- selectedJobTypes.length === 0 ||
- selectedJobTypes.some((t) => H_CAPTCHA_ORACLE.jobTypes.includes(t))
- ) {
- oracles.push(H_CAPTCHA_ORACLE);
+ if (selectedJobTypes.length === 0) {
+ oracles.push(...MOCKED_ORACLES);
}
if (env.VITE_FEATURE_FLAG_JOBS_DISCOVERY) {
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)}
+
+
+ );
+ })}
+
+
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/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 },
}}
>
theme.zIndex.drawer,
+ }}
slotProps={{
backdrop: {
sx: {
@@ -54,7 +57,7 @@ export function BaseDrawer({
position: 'absolute',
top: 16,
right: 16,
- p: 0,
+ p: 0.5,
...closeButtonSx,
}}
>
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..04c5512bc8 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,10 @@ 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';
function HumanLogoIcon() {
const { isDarkMode } = useColorMode();
@@ -78,10 +80,6 @@ function MoonIcon() {
const { isDarkMode } = useColorMode();
return isDarkMode ? : ;
}
-function CopyIcon() {
- const { isDarkMode } = useColorMode();
- return isDarkMode ? : ;
-}
function EditIcon() {
const { isDarkMode } = useColorMode();
return isDarkMode ? : ;
@@ -90,6 +88,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 +160,106 @@ 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 (
+
+
+
+
+
+ );
}
export {
@@ -172,4 +282,10 @@ export {
VeriffIcon,
HourglassIcon,
LogoutIcon,
+ TriangleIcon,
+ OracleAddressIcon,
+ OracleRewardIcon,
+ MenuIcon,
+ HcaptchaIcon,
+ HcaptchaDisabledIcon,
};
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..97302e8707 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
@@ -52,7 +52,8 @@ export function BaseModal({
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/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/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-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..3f625a82e1 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,9 +283,10 @@
"completeButton": "Complete"
},
"jobs": {
+ "hCaptcha": "hCaptcha",
"successFullyAssignedJob": "Successfully assigned a task!",
"errorFetchingData": "There was an error while fetching data, please try again",
- "availableJobs": "Available Tasks",
+ "availableJobs": "Available Jobs",
"myJobs": "My Tasks",
"jobsDiscovery": "Jobs Discovery",
"jobDescription": "Job description",
@@ -310,7 +334,10 @@
},
"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 96dbe6571e..7541c72350 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 17a8511898..7332fdc271 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',
@@ -74,11 +75,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: {
diff --git a/packages/apps/human-app/frontend/src/shared/styles/typography.ts b/packages/apps/human-app/frontend/src/shared/styles/typography.ts
index d14f247a98..cc67dc5bd0 100644
--- a/packages/apps/human-app/frontend/src/shared/styles/typography.ts
+++ b/packages/apps/human-app/frontend/src/shared/styles/typography.ts
@@ -103,7 +103,7 @@ export const typography: ThemeOptions['typography'] = {
h6: {
fontSize: 20,
fontWeight: 500,
- letterSpacing: 0.15,
+ letterSpacing: 0.12,
[breakpoints.mobile]: {
fontSize: 20,
},
@@ -127,15 +127,12 @@ export const typography: ThemeOptions['typography'] = {
body1: {
fontSize: 16,
fontWeight: 400,
- letterSpacing: 0.15,
+ letterSpacing: 0.12,
},
body2: {
fontSize: 14,
fontWeight: 400,
- letterSpacing: 0.15,
- [breakpoints.mobile]: {
- fontSize: 14,
- },
+ letterSpacing: 0.12,
},
body4: {
fontSize: 24,