Files
TheGame/packages/web/components/Player/Section/PlayerDework.tsx

504 lines
14 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
Box,
Button,
ExternalLinkIcon,
FormControl,
FormHelperText,
FormLabel,
Heading,
Image,
InfoIcon,
Input,
Link,
Spinner,
Text,
Tooltip,
VStack,
Wrap,
WrapItem,
} from '@metafam/ds';
import { generateUUID } from '@metafam/utils';
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { MdRefresh } from 'react-icons/md';
import {
DeworkData,
getDeworkData,
type Organisation,
processDeworkData,
} from 'utils/dework';
import DeworkLogo from '#assets/integrationLogos/deworkLogo.png';
import { MetaLink } from '#components/Link';
import { ProfileSection } from '#components/Section/ProfileSection';
import { Player, useUpsertDeworkProfileMutation } from '#graphql/autogen/hasura-sdk';
import { getPlayerDeworkUsername } from '#graphql/getDeworkUsername';
import { BoxTypes } from '#utils/boxTypes';
import { formatAddress } from '#utils/playerHelpers';
type Props = {
player: Player;
isOwnProfile?: boolean;
editing?: boolean;
};
type DeworkSectionWrapperProps = {
children: React.ReactNode;
};
type DeworkSectionHeadingProps = {
text: string;
};
export const DeworkSectionWrapper: React.FC<DeworkSectionWrapperProps> = ({
children,
}: {
children: React.ReactNode;
}) => (
<VStack
display="inline-flex"
flex="0 0 50%"
maxWidth="50%"
textAlign="left"
alignItems="left"
mb={3}
gap={0}
>
{children}
</VStack>
);
export const DeworkSectionHeading: React.FC<DeworkSectionHeadingProps> = ({
text,
}: {
text: string;
}) => (
<Text fontSize="md" color="blueLight" mb={0} whiteSpace="nowrap">
{`${text}`}
</Text>
);
export const PlayerDework: React.FC<Props> = ({
player,
isOwnProfile,
editing,
}) => {
const [role, setRole] = useState<string>('AddURL');
const [playerDeworkURL, setPlayerDeworkURL] = useState<string>('');
const [addressMatch, setAddressMatch] = useState<boolean>(false);
const [deworkData, setDeworkData] = useState<DeworkData>();
const connected = addressMatch && !playerDeworkURL;
const [loading, setLoading] = useState<boolean>(true);
const noticeRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const getDeworkUsername = async () => {
getPlayerDeworkUsername(player.id).then(
(res) => res && setPlayerDeworkURL(res as string),
);
};
if (player?.id) {
getDeworkUsername();
}
}, [player?.id, player.accounts]);
const getData = useCallback(async (address: string) => {
setLoading(true);
await getDeworkData(address).then((res: DeworkData) => {
setDeworkData(res);
const lowerCaseAddress = address.toLowerCase();
const lowerCaseResAddress = res?.address?.toLowerCase();
const hasTasks = res?.tasks?.length;
// test if address matches dework data
if (lowerCaseResAddress === lowerCaseAddress && hasTasks) {
setAddressMatch(true);
} else {
setAddressMatch(false);
}
setLoading(false);
});
}, []);
useEffect(() => {
getData(player?.ethereumAddress);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [player?.ethereumAddress]);
const retryCall = async () => {
await getData(player?.ethereumAddress);
};
useEffect(() => {
if (typeof window !== 'undefined') {
// find the parent div of the noticeRef and position noticeRef at the top
const parentDiv = noticeRef.current?.parentElement;
if (parentDiv) {
parentDiv.style.position = 'relative';
parentDiv.style.top = '0';
}
}
}, []);
useMemo(() => {
if (loading) {
setRole('Loading');
} else if (playerDeworkURL) {
setRole('DeworkProfile');
} else if (addressMatch && !playerDeworkURL) {
setRole('AddURL');
} else {
setRole('NoMatch');
}
}, [loading, playerDeworkURL, addressMatch]);
return (
<ProfileSection
title="Dework Profile"
{...{ isOwnProfile, editing, connected, player }}
type={BoxTypes.DEWORK}
>
<Box position={'relative'} m={0}>
<PlayerDeworkView
role={role}
player={player}
playerDeworkURL={playerDeworkURL}
setPlayerDeworkURL={setPlayerDeworkURL}
profileData={deworkData}
retry={() => retryCall()}
/>
{role !== 'NoMatch' && (
<Image
src={DeworkLogo.src}
height="35px"
width="35px"
mx="auto"
position="absolute"
bottom={0}
right={0}
alt="Dework logo"
/>
)}
</Box>
</ProfileSection>
);
};
const DeworkProfile: React.FC<{
data?: DeworkData;
playerDeworkURL: string;
}> = ({ data, playerDeworkURL }) => {
const processedData = useMemo(() => data && processDeworkData(data), [data]);
const deworkURL = `https://app.dework.xyz/${
playerDeworkURL ? `profile/${playerDeworkURL}` : ''
}`;
return (
<Wrap>
<WrapItem
position="relative"
mb={0}
display={'flex'}
flexFlow={'row wrap'}
width="full"
>
{data && processedData && (
<>
<DeworkSectionWrapper>
<DeworkSectionHeading text="Contributions" />
<Text fontSize="2xl">{data?.tasks?.length}</Text>
</DeworkSectionWrapper>
<DeworkSectionWrapper>
<DeworkSectionHeading text="Earnings" />
<Text fontSize="2xl">
{processedData.totalEarnedInUSDC.toFixed(2)} USD
<br />
{Number(processedData.totalSEEDsEarned) > 0 && (
<>{processedData.totalSEEDsEarned.toFixed(2)} SEED </>
)}
</Text>
</DeworkSectionWrapper>
<DeworkSectionWrapper>
<DeworkSectionHeading text="Types of tasks" />
{processedData?.tagGrouping.length > 0 ? (
processedData?.tagGrouping.map(
(tag: { name: string; count: number }, i: number) => {
const total: number = processedData?.tagGrouping.length;
if (i < 3) {
return (
<React.Fragment key={`${tag.name}-${i}`}>
<Text
textTransform="capitalize"
fontSize={{ base: 'sm', xl: 'md' }}
>
<Box as="span" textOverflow="ellipsis">
{tag.name}
</Box>{' '}
({tag.count})
</Text>
{i === 2 && (
<Box as="i" fontSize="sm">{`+${
total - 3
} other`}</Box>
)}
</React.Fragment>
);
}
return undefined;
},
)
) : (
<Text>No tasks</Text>
)}
</DeworkSectionWrapper>
<DeworkSectionWrapper>
<DeworkSectionHeading text="Organizations" />
{processedData?.uniqueOrganisations.length > 0 ? (
processedData?.uniqueOrganisations.map(
(org: Organisation, i: number) => {
const total: number =
processedData?.uniqueOrganisations.length;
if (i < 3) {
return (
<React.Fragment key={`${org.name}-${i}`}>
<Text fontSize={{ base: 'sm', xl: 'md' }}>
{org.name}
</Text>
{i === 2 && <Box as="i">{`+${total - 3} other`}</Box>}
</React.Fragment>
);
}
return undefined;
},
)
) : (
<Text>No organizations</Text>
)}
</DeworkSectionWrapper>
</>
)}
{data && (
<Link
sx={{ textDecoration: 'underline', color: '#b1fcfe' }}
href={deworkURL}
fontWeight={500}
display="inline-flex"
alignItems="center"
isExternal
>
See{' '}
<Text as="span" display={{ base: 'none', xl: 'inline' }}>
&nbsp;complete&nbsp;
</Text>{' '}
profile on Dework <ExternalLinkIcon mx="2px" />
</Link>
)}
</WrapItem>
</Wrap>
);
};
const PlayerDeworkView: React.FC<{
role: string;
player: Player;
playerDeworkURL: string;
setPlayerDeworkURL: (url: string) => void;
profileData?: DeworkData;
retry: () => void;
}> = ({
role,
player,
setPlayerDeworkURL,
retry,
profileData,
playerDeworkURL,
}) => {
const currentView = {
AddURL: (
<DeworkLink
setPlayerDeworkURL={setPlayerDeworkURL}
playerDeworkURL={playerDeworkURL}
player={player}
/>
),
Loading: <Loading />,
NoMatch: <NoMatch playerAddress={player.ethereumAddress} retry={retry} />,
DeworkProfile: (
<DeworkProfile data={profileData} playerDeworkURL={playerDeworkURL} />
),
}[role];
return <>{currentView}</>;
};
const DeworkLink: React.FC<{
setPlayerDeworkURL: (url: string) => void;
playerDeworkURL: string;
player: Player;
}> = ({ player, setPlayerDeworkURL, playerDeworkURL }) => {
const [deworkURL, setDeworkURL] = useState<string>('');
const [, upsertPlayerDework] = useUpsertDeworkProfileMutation();
const handleSetUserDeworkURL = async () => {
try {
const response = await upsertPlayerDework({
playerId: player?.id,
identifier: deworkURL,
});
// Handle the response if necessary.
// For example, you might want to update the UI based on the mutation's result:
if (response && response.data) {
setPlayerDeworkURL(deworkURL);
}
} catch (error) {
throw Error(
`Error upserting the Dework profile: ${(error as Error).message}`,
);
}
};
return (
<>
<FormControl id="deworkURL" mb={4}>
<FormLabel>
Input Dework Username
<Tooltip
label="It is not currently possible to obtain the username from the Dework API. Please enter your Dework username exactly as it appears on Dework."
aria-label="It is not currently possible to obtain the username from the Dework API. Please enter your Dework username exactly as it appears on Dework."
placement="top"
bgColor={'purple.400'}
hasArrow
>
<InfoIcon ml={2} />
</Tooltip>
</FormLabel>
<Input
value={deworkURL}
pl={2}
type="text"
inputMode="text"
placeholder="Example: Sero | Hunters Workshop"
step="any"
onChange={({ target: { value } }) => setDeworkURL(value)}
/>
<FormHelperText color="white">
Enter exactly as written on Dework
</FormHelperText>
</FormControl>
<Button
// onClick={() => setPlayerDeworkURL(deworkURL)}
onClick={() => handleSetUserDeworkURL()}
_disabled={{
cursor: 'not-allowed',
}}
>
{playerDeworkURL ? 'Proceed To Block' : 'Please Enter Dework username'}
</Button>
</>
);
};
const NoMatch: React.FC<{ playerAddress: string; retry: () => void }> = ({
playerAddress,
retry,
}) => (
<Box
position="relative"
display="flex"
justifyContent="center"
alignItems="center"
flexDirection="column"
textAlign="left"
p={6}
pb={14}
gap={4}
height="100%"
width="full"
_after={{
content: '""',
position: 'absolute',
inset: 0,
bottom: 10,
height: 'calc(100% - 2rem)',
zIndex: -1,
bgColor: 'red.500',
opacity: 0.25,
}}
>
<Heading as="h4" fontFamily="body" fontSize="md" textAlign="left" w="full">
<InfoIcon mr={2} /> Wallets do not match{' '}
</Heading>
<Text>
We couldnt find a Dework profile with the ETH address you used to connect
with MetaGame.
</Text>
<Text>
Go to your{' '}
<MetaLink
href={'https://app.dework.xyz/'}
color="white"
textDecoration="underline"
isExternal
>
Dework
</MetaLink>{' '}
profile and make sure you have connected with wallet{' '}
<strong>{formatAddress(playerAddress)}</strong>.
</Text>
<Text>
Connecting to a different wallet on Dework wont result in any data loss
or other issues.
</Text>
<Button
mt={4}
colorScheme="whiteAlpha"
color="white"
borderColor="white"
display="flex"
alignItems="center"
justifyContent="center"
variant="outline"
borderRadius={0}
w="full"
onClick={() => retry()}
leftIcon={<MdRefresh />}
>
Try Again
</Button>
<MetaLink
href={'https://app.dework.xyz/'}
color="white"
textDecoration="underline"
display="block"
position="absolute"
bottom={0}
transform="auto"
translateY={2}
isExternal
>
Go to Dework <ExternalLinkIcon ml={1} />
</MetaLink>
</Box>
);
const Loading: React.FC = () => (
<Box
display="flex"
justifyContent="flex-start"
alignItems="center"
height="100%"
width="full"
>
<Spinner size="lg" color="white" thickness="3px" flexShrink={0} />
<Text ml={4} pr={16} flexGrow={1}>
Looking for a Dework profile with the same ETH address
</Text>
</Box>
);