diff --git a/packages/apps-routing/src/claims.ts b/packages/apps-routing/src/claims.ts index 1e6a144685d6..42a63c4cb427 100644 --- a/packages/apps-routing/src/claims.ts +++ b/packages/apps-routing/src/claims.ts @@ -4,7 +4,7 @@ import { Route } from './types'; -import Claims from '@polkadot/app-claims'; +import Claims, { useCounter } from '@polkadot/app-claims'; export default function create (t: (key: string, text: string, options: { ns: string }) => T): Route { return { @@ -17,6 +17,7 @@ export default function create (t: (key: string, text: string, opti }, icon: 'star', name: 'claims', - text: t('nav.claims', 'Claim Tokens', { ns: 'apps-routing' }) + text: t('nav.claims', 'Claim Tokens', { ns: 'apps-routing' }), + useCounter }; } diff --git a/packages/page-claims/src/Attest.tsx b/packages/page-claims/src/Attest.tsx new file mode 100644 index 000000000000..5416278c0fdb --- /dev/null +++ b/packages/page-claims/src/Attest.tsx @@ -0,0 +1,87 @@ +// Copyright 2017-2020 @polkadot/app-claims authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import React, { useEffect, useState } from 'react'; +import styled from 'styled-components'; +import { Button, Card, TxButton } from '@polkadot/react-components'; +import { TxCallback } from '@polkadot/react-components/Status/types'; +import { useApi } from '@polkadot/react-hooks'; +import { FormatBalance } from '@polkadot/react-query'; +import { Option } from '@polkadot/types'; +import { BalanceOf, EthereumAddress, StatementKind } from '@polkadot/types/interfaces'; + +import { ClaimStyles } from './Claim'; +import Statement from './Statement'; +import { useTranslation } from './translate'; +import { getStatement } from './util'; + +interface Props { + accountId: string; + className?: string; + ethereumAddress: EthereumAddress | null; + onSuccess?: TxCallback; + statementKind?: StatementKind; + systemChain: string; +} + +function Attest ({ accountId, className, ethereumAddress, onSuccess, statementKind, systemChain }: Props): React.ReactElement | null { + const { t } = useTranslation(); + const { api } = useApi(); + const [claimValue, setClaimValue] = useState(null); + const [isBusy, setIsBusy] = useState(false); + + useEffect((): void => { + if (!ethereumAddress) { + return; + } + + setIsBusy(true); + + api.query.claims + .claims>(ethereumAddress) + .then((claim): void => { + setClaimValue(claim.unwrapOr(null)); + setIsBusy(false); + }) + .catch((): void => setIsBusy(false)); + }, [api, ethereumAddress]); + + if (isBusy) { + return null; + } + + const hasClaim = claimValue && claimValue.gten(0); + const statementSentence = getStatement(systemChain, statementKind)?.sentence; + + if (!hasClaim || !statementSentence) { + return null; + } + + return ( + +
+ +

('Account balance:')} + value={claimValue} />

+ + ('I agree')} + onSuccess={onSuccess} + params={[statementSentence]} + tx='claims.attest' + /> + +
+
+ ); +} + +export default React.memo(styled(Attest)`${ClaimStyles}`); diff --git a/packages/page-claims/src/Claim.tsx b/packages/page-claims/src/Claim.tsx index ab2c6fa8117d..cf55a636ae5b 100644 --- a/packages/page-claims/src/Claim.tsx +++ b/packages/page-claims/src/Claim.tsx @@ -3,52 +3,75 @@ // of the Apache-2.0 license. See the LICENSE file for details. import { Option } from '@polkadot/types'; -import { BalanceOf, EthereumAddress } from '@polkadot/types/interfaces'; +import { BalanceOf, EthereumAddress, StatementKind } from '@polkadot/types/interfaces'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; -import { Button, Card } from '@polkadot/react-components'; +import { Button, Card, TxButton } from '@polkadot/react-components'; +import { TxCallback } from '@polkadot/react-components/Status/types'; import { useApi } from '@polkadot/react-hooks'; import { FormatBalance } from '@polkadot/react-query'; import { useTranslation } from './translate'; -import { addrToChecksum } from './util'; +import { addrToChecksum, getStatement } from './util'; interface Props { - button: React.ReactNode; + accountId: string; className?: string; ethereumAddress: EthereumAddress | null; + ethereumSignature: string | null; + // Do we sign with `claims.claimAttest` (new) instead of `claims.claim` (old)? + isOldClaimProcess: boolean; + onSuccess?: TxCallback; + statementKind?: StatementKind; + systemChain: string; } -function Claim ({ button, className = '', ethereumAddress }: Props): React.ReactElement | null { +interface ConstructTx { + params?: any[]; + tx?: string; +} + +// Depending on isOldClaimProcess, construct the correct tx. +function constructTx ( + systemChain: string, + accountId: string, + ethereumSignature: string | null, + kind: StatementKind | undefined, + isOldClaimProcess: boolean +): ConstructTx { + if (!ethereumSignature || !kind) { + return {}; + } + + return isOldClaimProcess + ? { params: [accountId, ethereumSignature], tx: 'claims.claim' } + : { params: [accountId, ethereumSignature, getStatement(systemChain, kind)?.sentence], tx: 'claims.claimAttest' }; +} + +function Claim ({ accountId, className = '', ethereumAddress, ethereumSignature, isOldClaimProcess, onSuccess, statementKind, systemChain }: Props): React.ReactElement | null { const { t } = useTranslation(); const { api } = useApi(); const [claimValue, setClaimValue] = useState(null); - const [claimAddress, setClaimAddress] = useState(null); const [isBusy, setIsBusy] = useState(false); - const _fetchClaim = useCallback( - (address: EthereumAddress): void => { - setIsBusy(true); - - api.query.claims - .claims>(address) - .then((claim): void => { - setClaimValue(claim.unwrapOr(null)); - setIsBusy(false); - }) - .catch((): void => setIsBusy(false)); - }, - [api] - ); - useEffect((): void => { - setClaimAddress(ethereumAddress); + if (!ethereumAddress) { + return; + } - ethereumAddress && _fetchClaim(ethereumAddress); - }, [_fetchClaim, ethereumAddress]); + setIsBusy(true); - if (isBusy || !claimAddress) { + api.query.claims + .claims>(ethereumAddress) + .then((claim): void => { + setClaimValue(claim.unwrapOr(null)); + setIsBusy(false); + }) + .catch((): void => setIsBusy(false)); + }, [api, ethereumAddress]); + + if (!ethereumAddress || isBusy) { return null; } @@ -61,13 +84,23 @@ function Claim ({ button, className = '', ethereumAddress }: Props): React.React >
{t('Your Ethereum account')} -

{addrToChecksum(claimAddress.toString())}

+

{addrToChecksum(ethereumAddress.toString())}

{hasClaim && claimValue ? ( <> {t('has a valid claim for')}

- {button} + + + ) : ( @@ -80,29 +113,31 @@ function Claim ({ button, className = '', ethereumAddress }: Props): React.React ); } -export default React.memo(styled(Claim)` - font-size: 1.15rem; - display: flex; - flex-direction: column; - justify-content: center; - min-height: 12rem; - align-items: center; - margin: 0 1rem; - - h3 { - font-family: monospace; - font-size: 1.5rem; - max-width: 100%; - margin: 0.5rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } +export const ClaimStyles = ` +font-size: 1.15rem; +display: flex; +flex-direction: column; +justify-content: center; +min-height: 12rem; +align-items: center; +margin: 0 1rem; - h2 { - margin: 0.5rem 0 2rem; - font-family: monospace; - font-size: 2.5rem; - font-weight: 200; - } -`); +h3 { + font-family: monospace; + font-size: 1.5rem; + max-width: 100%; + margin: 0.5rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +h2 { + margin: 0.5rem 0 2rem; + font-family: monospace; + font-size: 2.5rem; + font-weight: 200; +} +`; + +export default React.memo(styled(Claim)`${ClaimStyles}`); diff --git a/packages/page-claims/src/Statement.tsx b/packages/page-claims/src/Statement.tsx new file mode 100644 index 000000000000..3101c8d44d85 --- /dev/null +++ b/packages/page-claims/src/Statement.tsx @@ -0,0 +1,82 @@ +// Copyright 2017-2020 @polkadot/app-claims authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { StatementKind } from '@polkadot/types/interfaces'; + +import React from 'react'; +import styled from 'styled-components'; + +import { useTranslation } from './translate'; +import { getStatement } from './util'; + +export interface Props { + className?: string; + kind?: StatementKind; + systemChain: string; +} + +// Get the full hardcoded text for a statement +function StatementFullText ({ statementUrl, systemChain }: { statementUrl?: string; systemChain: string }): React.ReactElement | null { + const { t } = useTranslation(); + + switch (systemChain) { + case 'Polkadot CC1': { + if (!statementUrl) { + return null; + } + + return