Skip to content

Commit

Permalink
Merge pull request #296 from 1Hive/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
Corantin committed May 31, 2022
2 parents 7979dbd + e4b5a48 commit 89660a2
Show file tree
Hide file tree
Showing 30 changed files with 758 additions and 780 deletions.
139 changes: 73 additions & 66 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 @@ -43,32 +44,73 @@ const BoxStyled = styled(Box)`
type Props = {
questData: QuestModel;
challengeDeposit: TokenAmountModel;
questTotalBounty?: TokenAmountModel | null;
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,
questTotalBounty,
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 @@ -89,42 +131,27 @@ export default function ClaimList({

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 @@ -133,28 +160,8 @@ export default function ClaimList({
<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}
questTotalBounty={questTotalBounty}
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
100 changes: 58 additions & 42 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 All @@ -31,55 +34,33 @@ const AddressWrapperStyled = styled.div<{ isSmallScreen: boolean }>`
type Props = {
claim: ClaimModel;
isLoading?: boolean;
questTotalBounty?: TokenAmountModel | null;
challengeDeposit: TokenAmountModel;
questData: QuestModel;
};

export default function Claim({
claim,
isLoading,
questTotalBounty,
challengeDeposit,
questData,
}: Props) {
const { walletAddress } = useWallet();
export default function Claim({ claim, isLoading, challengeDeposit, questData }: Props) {
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={questTotalBounty}
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 @@ -107,17 +88,52 @@ export default function Claim({
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 @@ -155,7 +171,7 @@ export default function Claim({
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
Loading

1 comment on commit 89660a2

@vercel
Copy link

@vercel vercel bot commented on 89660a2 May 31, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

quests – ./

quests.vercel.app
quests-git-main-1hive.vercel.app
quests.1hive.org
quests-1hive.vercel.app

Please sign in to comment.