diff --git a/packages/page-alliance/src/Member.tsx b/packages/page-alliance/src/Member.tsx index f189302363..52986e2540 100644 --- a/packages/page-alliance/src/Member.tsx +++ b/packages/page-alliance/src/Member.tsx @@ -3,9 +3,14 @@ import type { Member as MemberType } from './types'; -import React from 'react'; +import React, { useCallback, useContext, useMemo } from 'react'; -import { AddressSmall } from '@polkadot/react-components'; +import { AddressSmall, Badge, Menu, Popup, StatusContext } from '@polkadot/react-components'; +import { useAccounts, useApi } from '@polkadot/react-hooks'; +import { FormatBalance } from '@polkadot/react-query'; + +import { useTranslation } from './translate'; +import useMemberInfo from './useMemberInfo'; interface Props { className?: string; @@ -13,15 +18,62 @@ interface Props { } function Member ({ className, info: { accountId, role } }: Props): React.ReactElement { + const { t } = useTranslation(); + const { api } = useApi(); + const { allAccounts } = useAccounts(); + const info = useMemberInfo(accountId); + const { queueExtrinsic } = useContext(StatusContext); + + const hasActions = useMemo( + () => allAccounts.includes(accountId), + [allAccounts, accountId] + ); + + const doRetire = useCallback( + () => queueExtrinsic({ + accountId, + extrinsic: api.tx.alliance.retire() + }), + [accountId, api, queueExtrinsic] + ); + return ( - + + {info && info.isUpForKicking && ( + + )} + + + {info && info.deposit && ( + + )} + {role} + + {hasActions && ( + + ('Retire')} + onClick={doRetire} + /> + + } + /> + )} + ); } diff --git a/packages/page-alliance/src/Members.tsx b/packages/page-alliance/src/Members.tsx index bf496f7146..52de8bd358 100644 --- a/packages/page-alliance/src/Members.tsx +++ b/packages/page-alliance/src/Members.tsx @@ -23,7 +23,9 @@ function Members ({ className }: Props): React.ReactElement { const hdrRef = useRef([ [t('members'), 'start', 2], - [t('role'), 'number'] + [t('deposit'), 'number'], + [t('role'), 'number'], + [] ]); return ( diff --git a/packages/page-alliance/src/types.ts b/packages/page-alliance/src/types.ts index cb45dc2f75..a792c8dd13 100644 --- a/packages/page-alliance/src/types.ts +++ b/packages/page-alliance/src/types.ts @@ -2,8 +2,15 @@ // SPDX-License-Identifier: Apache-2.0 import type { PalletAllianceMemberRole } from '@polkadot/types/lookup'; +import type { BN } from '@polkadot/util'; export interface Member { accountId: string; role: PalletAllianceMemberRole['type']; } + +export interface MemberInfo { + accountId: string; + deposit?: BN | null; + isUpForKicking: boolean; +} diff --git a/packages/page-alliance/src/useMemberInfo.ts b/packages/page-alliance/src/useMemberInfo.ts new file mode 100644 index 0000000000..05c97d7009 --- /dev/null +++ b/packages/page-alliance/src/useMemberInfo.ts @@ -0,0 +1,29 @@ +// Copyright 2017-2022 @polkadot/app-alliance authors & contributors +// SPDX-License-Identifier: Apache-2.0 + +import type { bool, Option, UInt } from '@polkadot/types'; +import type { MemberInfo } from './types'; + +import { useMemo } from 'react'; + +import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks'; + +function useMemberInfoImpl (accountId: string): MemberInfo | undefined { + const { api } = useApi(); + const upForKicking = useCall(api.query.alliance.upForKicking, [accountId]); + const depositOf = useCall>(api.query.alliance.depositOf, [accountId]); + + return useMemo((): MemberInfo | undefined => { + if (upForKicking && depositOf) { + return { + accountId, + deposit: depositOf.unwrapOr(null), + isUpForKicking: upForKicking.isTrue + }; + } + + return undefined; + }, [accountId, upForKicking, depositOf]); +} + +export default createNamedHook('useMemberInfo', useMemberInfoImpl); diff --git a/packages/page-alliance/src/useMembers.ts b/packages/page-alliance/src/useMembers.ts index 9cc279523f..78c762817b 100644 --- a/packages/page-alliance/src/useMembers.ts +++ b/packages/page-alliance/src/useMembers.ts @@ -4,18 +4,28 @@ import type { AccountId32 } from '@polkadot/types/interfaces'; import type { Member } from './types'; -import { useMemo } from 'react'; +import { useEffect, useState } from 'react'; import { createNamedHook, useApi, useCallMulti } from '@polkadot/react-hooks'; const ROLES = ['Founder', 'Fellow', 'Ally']; -function addMembers (all: Member[], role: Member['role'], accountIds: AccountId32[] = []): Member[] { - for (let i = 0; i < accountIds.length; i++) { - all.push({ - accountId: accountIds[i].toString(), - role - }); +function addMembers (prev: Member[] = [], query: AccountId32[][]): Member[] { + const all: Member[] = []; + + for (let i = 0; i < ROLES.length; i++) { + const role = ROLES[i]; + const accountIds = query[i]; + + for (let j = 0; j < accountIds.length; j++) { + const accountId = accountIds[j].toString(); + const existing = prev.find((p) => p.accountId === accountId && p.role === role); + + all.push(existing || { + accountId, + role + }); + } } return all; @@ -23,25 +33,19 @@ function addMembers (all: Member[], role: Member['role'], accountIds: AccountId3 function useMembersImpl (): Member[] | undefined { const { api } = useApi(); + const [state, setState] = useState(); const query = useCallMulti([ [api.query.alliance.members, ROLES[0]], [api.query.alliance.members, ROLES[1]], [api.query.alliance.members, ROLES[2]] ]); - return useMemo((): Member[] | undefined => { - if (query && query.length === ROLES.length) { - const all: Member[] = []; - - for (let i = 0; i < ROLES.length; i++) { - addMembers(all, ROLES[i], query[i]); - } - - return all; - } - - return undefined; + useEffect((): void => { + query && query.length === ROLES.length && + setState((prev) => addMembers(prev, query)); }, [query]); + + return state; } -export default createNamedHook('useMembersImpl', useMembersImpl); +export default createNamedHook('useMembers', useMembersImpl);