Skip to content

Commit

Permalink
Merge pull request #291 from 1Hive/bug/timer-refactor
Browse files Browse the repository at this point in the history
Bug/timer refactor
  • Loading branch information
Corantin committed May 29, 2022
2 parents 9b7ae45 + cd5f7f2 commit b8fcf66
Show file tree
Hide file tree
Showing 26 changed files with 593 additions and 709 deletions.
132 changes: 73 additions & 59 deletions packages/react-app/src/components/claim-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { Accordion, Box } from '@1hive/1hive-ui';
import { ClaimModel } from 'src/models/claim.model';
import styled from 'styled-components';
import { GUpx } from 'src/utils/style.util';
import { ENUM_CLAIM_STATE, ENUM_TRANSACTION_STATUS } from 'src/constants';
import { ENUM_TRANSACTION_STATUS } from 'src/constants';
import { TokenAmountModel } from 'src/models/token-amount.model';
import { QuestModel } from 'src/models/quest.model';
import { useEffect, useState } from 'react';
import { getObjectFromIpfsSafe } from 'src/services/ipfs.service';
import { useEffect, useMemo, useState } from 'react';
import { getObjectFromIpfs, ipfsTheGraph } from 'src/services/ipfs.service';
import { useTransactionContext } from 'src/contexts/transaction.context';
import { useIsMountedRef } from 'src/hooks/use-mounted.hook';
import { HelpTooltip } from './field-input/help-tooltip';
import * as QuestService from '../services/quest.service';
import Claim from './claim';
Expand Down Expand Up @@ -46,23 +47,70 @@ type Props = {
isLoading?: boolean;
};

const loadingClaim = { state: ENUM_CLAIM_STATE.None } as ClaimModel;
const EvidenceRender = ({ claim, isLoading }: { claim: ClaimModel; isLoading: boolean }) => {
const [evidence, setEvidence] = useState<string>();
const isMountedRef = useIsMountedRef();

export default function ClaimList({ questData, challengeDeposit, isLoading = false }: Props) {
const [claims, setClaims] = useState<ClaimModel[]>([loadingClaim]);
const [isLoadingState, setIsLoading] = useState(isLoading);
const { transaction } = useTransactionContext();
let isMounted = true;
useEffect(
() => () => {
isMounted = false;
},
[],
useEffect(() => {
if (!evidence) fetchEvidence();
}, [claim.evidenceIpfsHash, evidence]);

const fetchEvidence = async () => {
const evidenceResult = claim.evidenceIpfsHash
? await getObjectFromIpfs(claim.evidenceIpfsHash, ipfsTheGraph)
: 'No evidence';
if (isMountedRef.current) {
setEvidence(evidenceResult);
}
};

return (
<Outset gu16>
<TextFieldInput
id="evidence"
value={evidence}
isMarkDown
wide
label="Evidence of completion"
isLoading={isLoading || !evidence}
/>
</Outset>
);
};

useEffect(() => {
setIsLoading(isLoading);
}, [isLoading]);
export default function ClaimList({ questData, challengeDeposit, isLoading = false }: Props) {
const [claims, setClaims] = useState<ClaimModel[]>([]);
const [loadingClaim, setLoadingClaim] = useState(true);
const { transaction } = useTransactionContext();
const isMountedRef = useIsMountedRef();
const skeletonClaim = useMemo(() => {
const fakeClaim = {} as ClaimModel;
return [
<Claim
isLoading
claim={fakeClaim}
questData={questData}
challengeDeposit={challengeDeposit}
/>,
<EvidenceRender isLoading claim={fakeClaim} />,
];
}, []);

const accordionItems = useMemo(() => {
const items = claims.map((claim: ClaimModel) => [
<Claim
claim={claim}
challengeDeposit={challengeDeposit}
questData={questData}
isLoading={isLoading}
/>,
<EvidenceRender claim={claim} isLoading={isLoading} />,
]);
if (loadingClaim) {
items.unshift(skeletonClaim);
}
return items;
}, [claims, loadingClaim, questData.bounty, isLoading, challengeDeposit]);

useEffect(() => {
if (questData.address) {
Expand All @@ -83,42 +131,27 @@ export default function ClaimList({ questData, challengeDeposit, isLoading = fal

const fetchClaimsUntilNew = (claimsCount?: number) => {
if (!claimsCount) {
setClaims([loadingClaim, ...claims]);
setLoadingClaim(true);
claimsCount = claims.length;
}
setTimeout(async () => {
const results = await QuestService.fetchQuestClaims(questData);
if (!isMounted) return;
if (!isMountedRef.current) return;
if (results.length === claimsCount) {
fetchClaimsUntilNew(claimsCount);
} else {
setClaims(results);
setIsLoading(false);
fetchEvidenceOfCompletions(results);
setLoadingClaim(false);
}
}, 1000);
};

const fetchClaims = async () => {
setIsLoading(true);
setLoadingClaim(true);
const results = await QuestService.fetchQuestClaims(questData);
if (!isMounted) return;
if (!isMountedRef.current) return;
setClaims(results); // Fetch visible data
setIsLoading(false);
await fetchEvidenceOfCompletions(results);
};

const fetchEvidenceOfCompletions = async (result: ClaimModel[]) => {
const claimsRes = await Promise.all(
result.map(async (claim) => ({
...claim,
evidence: claim.evidenceIpfsHash
? await getObjectFromIpfsSafe(claim.evidenceIpfsHash)
: 'No evidence',
})),
);
if (!isMounted) return;
setClaims(claimsRes); // Fetch evidence wich is currently hidden in accordion
setLoadingClaim(false);
};

return (
Expand All @@ -127,27 +160,8 @@ export default function ClaimList({ questData, challengeDeposit, isLoading = fal
<HeaderStyled>Claims</HeaderStyled>
<HelpTooltip tooltip="A claim includes the proof of the quest's completion." />
</ClaimHeaderStyled>
{claims?.length || isLoadingState ? (
<Accordion
items={claims.map((claim: ClaimModel) => [
<Claim
claim={claim}
challengeDeposit={challengeDeposit}
isLoading={isLoadingState}
questData={questData}
/>,
<Outset gu8>
<TextFieldInput
id="evidence"
value={claim.evidence}
isMarkDown
wide
label="Evidence of completion"
isLoading={isLoading || !claim.evidence}
/>
</Outset>,
])}
/>
{claims?.length || loadingClaim ? (
<Accordion items={accordionItems} />
) : (
<BoxStyled>
<i>No claims</i>
Expand Down
91 changes: 57 additions & 34 deletions packages/react-app/src/components/claim.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { useViewport } from '@1hive/1hive-ui';
import { ReactNode, useEffect, useState } from 'react';
import { useViewport, Timer } from '@1hive/1hive-ui';
import { ReactNode, useEffect, useMemo, useState } from 'react';
import { ENUM_CLAIM_STATE, ENUM_DISPUTE_STATES, ENUM_TRANSACTION_STATUS } from 'src/constants';
import { useTransactionContext } from 'src/contexts/transaction.context';
import { useWallet } from 'src/contexts/wallet.context';
import { useIsMountedRef } from 'src/hooks/use-mounted.hook';
import { useNow } from 'src/hooks/use-now.hook';
import { ClaimModel } from 'src/models/claim.model';
import { QuestModel } from 'src/models/quest.model';
import { TokenAmountModel } from 'src/models/token-amount.model';
import { compareCaseInsensitive } from 'src/utils/string.util';
import styled, { css } from 'styled-components';
import { AddressFieldInput } from './field-input/address-field-input';
import AmountFieldInput from './field-input/amount-field-input';
Expand Down Expand Up @@ -36,43 +39,28 @@ type Props = {
};

export default function Claim({ claim, isLoading, challengeDeposit, questData }: Props) {
const { walletAddress } = useWallet();
const { walletAddress, walletConnected } = useWallet();
const { transaction } = useTransactionContext();
const [state, setState] = useState(claim.state);
const { below } = useViewport();
const [waitForClose, setWaitForClose] = useState(false);
const [actionButton, setActionButton] = useState<ReactNode>();
const isMountedRef = useIsMountedRef();
const now = useNow();
const claimable = useMemo(() => !!claim.executionTimeMs && claim.executionTimeMs <= now, [now]);

useEffect(() => {
setState(claim.state);
}, [claim.state]);

useEffect(() => {
if (waitForClose || !state) return;
if (state === ENUM_CLAIM_STATE.Scheduled) {
if (walletAddress === claim.playerAddress) {
setActionButton(
<ExecuteClaimModal
claim={claim}
questTotalBounty={questData.bounty}
onClose={onActionClose}
/>,
);
} else {
setActionButton(
<ChallengeModal
claim={claim}
challengeDeposit={challengeDeposit}
onClose={onActionClose}
/>,
);
}
} else if (state === ENUM_CLAIM_STATE.Challenged) {
setActionButton(<ResolveChallengeModal claim={claim} onClose={onActionClose} />);
} else {
setActionButton(undefined);
}
}, [state, walletAddress, waitForClose]);
const timer = useMemo(
() => !claimable && claim.executionTimeMs && <Timer end={new Date(claim.executionTimeMs)} />,
[claim.executionTimeMs, claimable],
);

const onActionClose = () => {
setWaitForClose(false);
};

useEffect(() => {
// If tx completion impact Claims, update them
Expand Down Expand Up @@ -100,17 +88,52 @@ export default function Claim({ claim, isLoading, challengeDeposit, questData }:
setState(ENUM_CLAIM_STATE.Executed);
break;
case 'ClaimChallenge':
setState(ENUM_CLAIM_STATE.Challenged);
setTimeout(() => {
setState(ENUM_CLAIM_STATE.Challenged);
}, 1000); // Wait for subgrapph to index challenge event
break;
default:
}
}
}
}, [transaction?.status, transaction?.type, transaction?.[0], claim.container]);

const onActionClose = () => {
setWaitForClose(false);
};
useEffect(() => {
if (waitForClose || !state || !isMountedRef.current) return;
if (state === ENUM_CLAIM_STATE.Scheduled) {
if (compareCaseInsensitive(walletAddress, claim.playerAddress) || claimable) {
setActionButton(
<>
<ExecuteClaimModal
claim={claim}
questTotalBounty={questData.bounty}
onClose={onActionClose}
claimable={claimable}
/>
{timer}
</>,
);
return;
}
setActionButton(
<>
<ChallengeModal
claim={claim}
challengeDeposit={challengeDeposit}
onClose={onActionClose}
/>
{timer}
</>,
);
return;
}

if (state === ENUM_CLAIM_STATE.Challenged) {
setActionButton(<ResolveChallengeModal claim={claim} onClose={onActionClose} />);
return;
}
setActionButton(undefined);
}, [state, walletAddress, waitForClose, claim, questData.bounty, challengeDeposit, claimable]);

return (
<div className="wide">
Expand Down Expand Up @@ -148,7 +171,7 @@ export default function Claim({ claim, isLoading, challengeDeposit, questData }:
isLoading={isLoading || state === ENUM_CLAIM_STATE.None}
/>
)}
{walletAddress && actionButton}
{walletConnected && state && actionButton}
</ChildSpacer>
</Outset>
</div>
Expand Down
6 changes: 3 additions & 3 deletions packages/react-app/src/components/dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getDashboardInfo } from 'src/services/quest.service';
import { GUpx } from 'src/utils/style.util';
import styled from 'styled-components';
import { FieldInput } from './field-input/field-input';
import QuestModal from './modals/quest-modal';
import QuestModal from './modals/create-quest-modal';
import { ChildSpacer } from './utils/spacer-util';

// #region StyledComponents
Expand Down Expand Up @@ -47,7 +47,7 @@ const LabelStyled = styled.div`
export default function Dashboard() {
const theme = useTheme();
const [dashboardModel, setDashboardModel] = useState<DashboardModel>();
const { walletAddress } = useWallet();
const { walletConnected } = useWallet();

useEffect(() => {
let isSubscribed = true;
Expand Down Expand Up @@ -84,7 +84,7 @@ export default function Dashboard() {
<TextStyled theme={theme}>{dashboardModel?.questCount.toLocaleString()}</TextStyled>
</FieldInput>
<SpacerStyled>
{walletAddress && <QuestModal questMode={ENUM_QUEST_VIEW_MODE.Create} />}
{walletConnected && <QuestModal questMode={ENUM_QUEST_VIEW_MODE.Create} />}
</SpacerStyled>
</ChildSpacer>
</BoxStyled>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { isAddress } from 'src/utils/web3.utils';
import styled from 'styled-components';
import { useCopyToClipboard } from 'src/hooks/use-copy-to-clipboard.hook';
import { NETWORK_TOKENS } from 'src/constants';
import { useIsMountedRef } from 'src/hooks/use-mounted.hook';
import { FieldInput } from './field-input';
import { ConditionalWrapper } from '../utils/util';

Expand Down Expand Up @@ -189,7 +190,7 @@ function AmountFieldInput({
showUsd = false,
error,
}: Props) {
let mounted = true;
const isMountedRef = useIsMountedRef();
const { networkId } = getNetwork();
const [tokens, setTokens] = useState<TokenModel[]>([]);
const [searchTerm, setSearchTerm] = useState<string>();
Expand All @@ -210,9 +211,6 @@ function AmountFieldInput({

useEffect(() => {
if (isEdit && !availableTokens.length) fetchAvailableTokens();
return () => {
mounted = false;
};
}, [isEdit]);

useEffect(() => {
Expand All @@ -226,7 +224,8 @@ function AmountFieldInput({
setTokens([]);
getTokenInfo(searchTerm)
.then((tokenInfo) => {
if (typeof tokenInfo !== 'string') if (tokenInfo && mounted) setTokens([tokenInfo]);
if (typeof tokenInfo !== 'string')
if (tokenInfo && isMountedRef.current) setTokens([tokenInfo]);
})
.catch(Logger.exception);
} else {
Expand Down Expand Up @@ -261,7 +260,7 @@ function AmountFieldInput({
const fetchAvailableTokens = async () => {
const networkDefaultTokens = (NETWORK_TOKENS[networkId] as TokenModel[]) ?? [];
const questsUsedTokens = await fetchRewardTokens();
if (mounted) {
if (isMountedRef.current) {
setAvailableTokens(
arrayDistinctBy([...networkDefaultTokens, ...questsUsedTokens], (x) => x.token),
);
Expand Down
Loading

0 comments on commit b8fcf66

Please sign in to comment.