diff --git a/packages/react-components/src/AccountName.tsx b/packages/react-components/src/AccountName.tsx index 70535fc366..15a4406907 100644 --- a/packages/react-components/src/AccountName.tsx +++ b/packages/react-components/src/AccountName.tsx @@ -2,15 +2,14 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { DeriveAccountInfo } from '@polkadot/api-derive/types'; +import { DeriveAccountInfo, DeriveAccountRegistration } from '@polkadot/api-derive/types'; import { BareProps } from '@polkadot/react-api/types'; -import { AccountId, AccountIndex, Address, RegistrarInfo } from '@polkadot/types/interfaces'; +import { AccountId, AccountIndex, Address } from '@polkadot/types/interfaces'; import React, { useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import registry from '@polkadot/react-api/typeRegistry'; -import { useCall, useAccounts, useApi, useToggle } from '@polkadot/react-hooks'; -import { Option } from '@polkadot/types'; +import { useCall, useApi, useRegistrars, useToggle } from '@polkadot/react-hooks'; import { stringToU8a } from '@polkadot/util'; import { useTranslation } from './translate'; @@ -26,7 +25,8 @@ interface Props extends BareProps { label?: React.ReactNode; onClick?: () => void; override?: React.ReactNode; - toggle?: any; + // this is used by app-account/addresses to toggle editing + toggle?: boolean; value: AccountId | AccountIndex | Address | string | Uint8Array | null | undefined; } @@ -68,7 +68,7 @@ function defaultOrAddr (defaultName = '', _address: AccountId | AccountIndex | A return [[extracted, null], !isAddressExtracted, isAddressExtracted, false]; } -function extractName (address: AccountId | string, accountIndex?: AccountIndex, defaultName?: string): React.ReactNode { +function extractName (address: string, accountIndex?: AccountIndex, defaultName?: string): React.ReactNode { const [[displayFirst, displaySecond], isLocal, isAddress, isSpecial] = defaultOrAddr(defaultName, address, accountIndex); return ( @@ -90,132 +90,123 @@ function extractName (address: AccountId | string, accountIndex?: AccountIndex, ); } +function extractIdentity (address: string, identity: DeriveAccountRegistration, onJudge: undefined | (() => void), t: (key: string, opts?: object) => string): React.ReactNode { + const judgements = identity.judgements.filter(([, judgement]): boolean => !judgement.isFeePaid); + const isGood = judgements.some(([, judgement]): boolean => judgement.isKnownGood || judgement.isReasonable); + const isBad = judgements.some(([, judgement]): boolean => judgement.isErroneous || judgement.isLowQuality); + const waitCount = identity.judgements.length - judgements.length; + const hover = ( +
+
+ { + judgements.length + ? (judgements.length === 1 + ? t('1 judgement') + : t('{{count}} judgements', { replace: { count: judgements.length } }) + ) + : t('no judgements') + }{judgements.length ? ': ' : ''}{judgements.map(([, judgement]): string => judgement.toString()).join(', ')}{ + waitCount + ? t(' ({{count}} waiting)', { replace: { count: waitCount } }) + : '' + } +
+ + + {identity.parent && ( + + + + + )} + {DISPLAY_KEYS + .filter((key): boolean => !!identity[key as 'web']) + .map((key): React.ReactNode => ( + + + + + )) + } + +
{t('parent')}
{t(key)}{identity[key as 'web']}
+
+ ); + + const displayName = isGood + ? identity.display + : (identity.display || '').replace(/[^\x20-\x7E]/g, ''); + const displayParent = identity.displayParent + ? ( + isGood + ? identity.displayParent + : identity.displayParent.replace(/[^\x20-\x7E]/g, '') + ) + : undefined; + + nameCache.set(address, [false, displayParent ? [displayParent, displayName] : [displayName, null]]); + + return ( +
+ } + isInline + isSmall + isTooltip + onClick={onJudge} + type={ + isGood + ? 'green' + : isBad + ? 'brown' + : 'gray' + } + /> + { + displayParent + ? {displayParent}/{displayName} + : {displayName} + } +
+ ); +} + function AccountName ({ children, className, defaultName, label, onClick, override, style, toggle, value }: Props): React.ReactElement { const { t } = useTranslation(); const { api } = useApi(); - const { allAccounts } = useAccounts(); + const { isRegistrar, registrars } = useRegistrars(); const [isJudgementOpen, toggleJudgement] = useToggle(); - const registrars = useCall[]>(api.query.identity?.registrars, []); const info = useCall(api.derive.accounts.info as any, [value]); - const [isRegistrar, setIsRegistrar] = useState(false); const address = useMemo((): string => (value || '').toString(), [value]); const [name, setName] = useState((): React.ReactNode => extractName((value || '').toString(), undefined, defaultName)); - // determine if we have a registrar or not - registrars are allowed to approve - useEffect((): void => { - allAccounts && registrars && setIsRegistrar( - registrars - .map((registrar): string| null => - registrar.isSome - ? registrar.unwrap().account.toString() - : null - ) - .some((regId): boolean => !!regId && allAccounts.includes(regId)) - ); - }, [allAccounts, registrars]); - // set the actual nickname, local name, accountIndex, accountId useEffect((): void => { const { accountId, accountIndex, identity, nickname } = info || {}; + const cacheAddr = (accountId || address).toString(); if (api.query.identity?.identityOf) { - if (identity?.display) { - const judgements = identity.judgements.filter(([, judgement]): boolean => !judgement.isFeePaid); - const isGood = judgements.some(([, judgement]): boolean => judgement.isKnownGood || judgement.isReasonable); - const isBad = judgements.some(([, judgement]): boolean => judgement.isErroneous || judgement.isLowQuality); - const waitCount = identity.judgements.length - judgements.length; - const hover = ( -
-
- { - judgements.length - ? (judgements.length === 1 - ? t('1 judgement') - : t('{{count}} judgements', { replace: { count: judgements.length } }) - ) - : t('no judgements') - }{judgements.length ? ': ' : ''}{judgements.map(([, judgement]): string => judgement.toString()).join(', ')}{ - waitCount - ? t(' ({{count}} waiting)', { replace: { count: waitCount } }) - : '' - } -
- - - {identity.parent && ( - - - - - )} - {DISPLAY_KEYS - .filter((key): boolean => !!identity[key as 'web']) - .map((key): React.ReactNode => ( - - - - - )) - } - -
{t('parent')}
{t(key)}{identity[key as 'web']}
-
- ); - - const displayName = isGood - ? identity.display - : identity.display.replace(/[^\x20-\x7E]/g, ''); - const displayParent = identity.displayParent - ? ( - isGood - ? identity.displayParent - : identity.displayParent.replace(/[^\x20-\x7E]/g, '') - ) - : undefined; - - const name = ( -
- } - isInline - isSmall - isTooltip - onClick={isRegistrar ? toggleJudgement : undefined} - type={ - isGood - ? 'green' - : isBad - ? 'brown' - : 'gray' - } - /> - { - displayParent - ? {displayParent}/{displayName} - : {displayName} - } -
- ); - - nameCache.set((accountId || address).toString(), [false, displayParent ? [displayParent, displayName] : [displayName, null]]); - setName((): React.ReactNode => name); - } else { - setName((): React.ReactNode => extractName(accountId || address, accountIndex)); - } + setName((): React.ReactNode => + identity?.display + ? extractIdentity(cacheAddr, identity, isRegistrar ? toggleJudgement : undefined, t) + : extractName(cacheAddr, accountIndex) + ); } else if (nickname) { - nameCache.set((accountId || address).toString(), [false, [nickname, null]]); + nameCache.set(cacheAddr, [false, [nickname, null]]); + setName(nickname); } else { - setName(defaultOrAddr(defaultName, accountId || address, accountIndex)); + setName(defaultOrAddr(defaultName, cacheAddr, accountIndex)); } - }, [address, info, toggle]); + }, [address, info, isRegistrar, toggle]); return ( <> {isJudgementOpen && ( )} diff --git a/packages/react-components/src/AccountNameJudgement.tsx b/packages/react-components/src/AccountNameJudgement.tsx index f8064a9ae7..3f94e06021 100644 --- a/packages/react-components/src/AccountNameJudgement.tsx +++ b/packages/react-components/src/AccountNameJudgement.tsx @@ -3,11 +3,8 @@ // of the Apache-2.0 license. See the LICENSE file for details. import { BareProps } from '@polkadot/react-api/types'; -import { RegistrarInfo } from '@polkadot/types/interfaces'; import React, { useEffect, useState } from 'react'; -import { useCall, useApi } from '@polkadot/react-hooks'; -import { Option } from '@polkadot/types'; import { useTranslation } from './translate'; import Dropdown from './Dropdown'; @@ -18,6 +15,7 @@ import TxButton from './TxButton'; interface Props extends BareProps { address: string; + registrars: (string | null)[]; toggleJudgement: () => void; } @@ -30,10 +28,8 @@ const JUDGEMENT_ENUM = [ { value: 5, text: 'Low quality' } ]; -function AccountNameJudgement ({ address, toggleJudgement }: Props): React.ReactElement { +function AccountNameJudgement ({ address, registrars, toggleJudgement }: Props): React.ReactElement { const { t } = useTranslation(); - const { api } = useApi(); - const registrars = useCall[]>(api.query.identity?.registrars, []); const [judgementAccountId, setJudgementAccountId] = useState(null); const [judgementEnum, setJudgementEnum] = useState(2); // Reasonable const [registrarIndex, setRegistrarIndex] = useState(-1); @@ -42,13 +38,7 @@ function AccountNameJudgement ({ address, toggleJudgement }: Props): React.React useEffect((): void => { if (registrars && judgementAccountId) { setRegistrarIndex( - registrars - .map((registrar): string| null => - registrar.isSome - ? registrar.unwrap().account.toString() - : null - ) - .indexOf(judgementAccountId) + registrars.indexOf(judgementAccountId) ); } else { setRegistrarIndex(-1); diff --git a/packages/react-components/src/AddressSmall.tsx b/packages/react-components/src/AddressSmall.tsx index 02cda4946d..a0853b6f9d 100644 --- a/packages/react-components/src/AddressSmall.tsx +++ b/packages/react-components/src/AddressSmall.tsx @@ -15,7 +15,7 @@ interface Props { defaultName?: string; onClickName?: () => void; overrideName?: React.ReactNode; - toggle?: any; + toggle?: boolean; value?: string | Address | AccountId | null | Uint8Array; } diff --git a/packages/react-hooks/src/index.ts b/packages/react-hooks/src/index.ts index 01c2656886..3a963f4e3e 100644 --- a/packages/react-hooks/src/index.ts +++ b/packages/react-hooks/src/index.ts @@ -17,6 +17,7 @@ export { default as useModal } from './useModal'; export { default as useOwnEraRewards } from './useOwnEraRewards'; export { default as useOwnStashes, useOwnStashIds } from './useOwnStashes'; export { default as usePassword } from './usePassword'; +export { default as useRegistrars } from './useRegistrars'; export { default as useSudo } from './useSudo'; export { default as useToggle } from './useToggle'; export { default as useTx } from './useTx'; diff --git a/packages/react-hooks/src/useRegistrars.ts b/packages/react-hooks/src/useRegistrars.ts new file mode 100644 index 0000000000..fc3579455c --- /dev/null +++ b/packages/react-hooks/src/useRegistrars.ts @@ -0,0 +1,42 @@ +// Copyright 2017-2020 @polkadot/react-hooks 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 { RegistrarInfo } from '@polkadot/types/interfaces'; + +import { useEffect, useState } from 'react'; +import { Option } from '@polkadot/types'; + +import useAccounts from './useAccounts'; +import useApi from './useApi'; +import useCall from './useCall'; + +interface State { + isRegistrar: boolean; + registrars: (string | null)[]; +} + +export default function useRegistrars (): State { + const { api } = useApi(); + const { allAccounts } = useAccounts(); + const query = useCall[]>(api.query.identity?.registrars, []); + const [state, setState] = useState({ isRegistrar: false, registrars: [] }); + + // determine if we have a registrar or not - registrars are allowed to approve + useEffect((): void => { + if (allAccounts && query) { + const registrars = query.map((registrar): string| null => + registrar.isSome + ? registrar.unwrap().account.toString() + : null + ); + + setState({ + isRegistrar: registrars.some((registrar) => !!registrar && allAccounts.includes(registrar)), + registrars + }); + } + }, [allAccounts, query]); + + return state; +}