Add alliance join (#7915)

This commit is contained in:
Jaco 2022-07-27 07:45:50 +02:00 committed by GitHub
parent 89771038c8
commit 6980e1ebeb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 131 additions and 38 deletions

View File

@ -19,7 +19,7 @@ const SHA_COL = 'rgba(34, 36, 38, 0.12)';
const SHA_OFF = '5px';
function Grouping ({ className = '', isActive, name, routes }: Props): React.ReactElement<Props> {
if (routes.length === 1) {
if (routes.length === 1 && routes[0].group === 'settings') {
return (
<Item
className={isActive ? 'isActive' : ''}

View File

@ -0,0 +1,72 @@
// Copyright 2017-2022 @polkadot/app-alliance authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Member } from './types';
import React, { useMemo, useState } from 'react';
import { InputAddress, InputBalance, Modal, TxButton } from '@polkadot/react-components';
import { useAccounts, useApi } from '@polkadot/react-hooks';
import { useTranslation } from './translate';
interface Props {
className?: string;
members: Member[];
onClose: () => void;
}
function Join ({ className, members, onClose }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { allAccounts } = useAccounts();
const [accountId, setAccountId] = useState<string | null>(null);
const available = useMemo(
(): string[] =>
allAccounts.filter((a) =>
!members.some(({ accountId }) =>
accountId === a
)
),
[allAccounts, members]
);
return (
<Modal
className={className}
header={t<string>('Join alliance')}
onClose={onClose}
size='large'
>
<Modal.Content>
<Modal.Columns hint={t<string>('This account will be submitted to join the aliance. It will be allocated one of the alliance roles upon joining, starting with Ally.')}>
<InputAddress
filter={available}
help={t<string>('Select the account you wish to join with')}
label={t<string>('alliance account')}
onChange={setAccountId}
type='account'
/>
</Modal.Columns>
<Modal.Columns hint={t('The bond will be reserved for the duration of your alliance membership.')}>
<InputBalance
defaultValue={api.consts.alliance.allyDeposit}
help={t<string>('The deposit that is reserved')}
isDisabled
label={t<string>('alliance deposit')}
/>
</Modal.Columns>
</Modal.Content>
<Modal.Actions>
<TxButton
accountId={accountId}
onStart={onClose}
tx={api.tx.alliance.joinAlliance}
/>
</Modal.Actions>
</Modal>
);
}
export default React.memo(Join);

View File

@ -20,7 +20,7 @@ function Member ({ className, info: { accountId, role } }: Props): React.ReactEl
<AddressSmall value={accountId} />
</td>
<td className='number'>
{role.type}
{role}
</td>
</tr>
);

View File

@ -3,8 +3,10 @@
import React, { useRef } from 'react';
import { Table } from '@polkadot/react-components';
import { Button, Table } from '@polkadot/react-components';
import { useToggle } from '@polkadot/react-hooks';
import Join from './Join';
import Member from './Member';
import Summary from './Summary';
import { useTranslation } from './translate';
@ -14,8 +16,9 @@ interface Props {
className?: string;
}
function Collators ({ className }: Props): React.ReactElement<Props> {
function Members ({ className }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [isJoinOpen, toggleJoin] = useToggle();
const members = useMembers();
const hdrRef = useRef([
@ -26,6 +29,20 @@ function Collators ({ className }: Props): React.ReactElement<Props> {
return (
<div className={className}>
<Summary />
<Button.Group>
<Button
icon='add'
isDisabled={!members}
label={t<string>('Join')}
onClick={toggleJoin}
/>
{members && isJoinOpen && (
<Join
members={members}
onClose={toggleJoin}
/>
)}
</Button.Group>
<Table
empty={members && t<string>('No members')}
header={hdrRef.current}
@ -41,4 +58,4 @@ function Collators ({ className }: Props): React.ReactElement<Props> {
);
}
export default React.memo(Collators);
export default React.memo(Members);

View File

@ -5,5 +5,5 @@ import type { PalletAllianceMemberRole } from '@polkadot/types/lookup';
export interface Member {
accountId: string;
role: PalletAllianceMemberRole;
role: PalletAllianceMemberRole['type'];
}

View File

@ -1,43 +1,47 @@
// Copyright 2017-2022 @polkadot/app-alliance authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { StorageKey } from '@polkadot/types';
import type { AccountId32 } from '@polkadot/types/interfaces';
import type { PalletAllianceMemberRole } from '@polkadot/types/lookup';
import type { Member } from './types';
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
import { useMemo } from 'react';
const OPT_MEM = {
transform: (entries: [StorageKey<[PalletAllianceMemberRole]>, AccountId32[]][]) =>
entries
.reduce((all: Member[], [{ args: [role] }, accountIds]): Member[] => {
for (let i = 0; i < accountIds.length; i++) {
all.push({
accountId: accountIds[i].toString(),
role
});
}
import { createNamedHook, useApi, useCallMulti } from '@polkadot/react-hooks';
return all;
}, [])
.sort((a, b) =>
a.role.type === b.role.type
? 0
: a.role.type === 'Founder'
? -1
: b.role.type === 'Founder'
? 1
: a.role.type === 'Fellow'
? -1
: 1
)
};
const ROLES = <const> ['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
});
}
return all;
}
function useMembersImpl (): Member[] | undefined {
const { api } = useApi();
const query = useCallMulti<AccountId32[][]>([
[api.query.alliance.members, ROLES[0]],
[api.query.alliance.members, ROLES[1]],
[api.query.alliance.members, ROLES[2]]
]);
return useCall<Member[]>(api.query.alliance.members.entries, [], OPT_MEM);
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;
}, [query]);
}
export default createNamedHook('useMembersImpl', useMembersImpl);

View File

@ -14,7 +14,7 @@ import { useModuleElections } from '../useModuleElections';
function SubmitCandidacy ({ electionsInfo }: Props): React.ReactElement<Props> | null {
const { api } = useApi();
const { t } = useTranslation();
const [accountId, setAcountId] = useState<string | null>(null);
const [accountId, setAccountId] = useState<string | null>(null);
const { isOpen, onClose, onOpen } = useModal();
const modLocation = useModuleElections();
@ -35,7 +35,7 @@ function SubmitCandidacy ({ electionsInfo }: Props): React.ReactElement<Props> |
<InputAddress
help={t<string>('Select the account you wish to submit for candidacy.')}
label={t<string>('candidate account')}
onChange={setAcountId}
onChange={setAccountId}
type='account'
/>
</Modal.Columns>

View File

@ -27,7 +27,7 @@ interface Turnout {
voted: BN;
}
const optMulti = {
const OPT_MULTI = {
defaultValue: [undefined, undefined] as MultiResult
};
@ -38,7 +38,7 @@ function PollApp ({ basePath, className }: Props): React.ReactElement<Props> {
const [totalIssuance, totals] = useCallMulti<MultiResult>([
api.query.balances?.totalIssuance,
api.query.poll.totals
], optMulti);
], OPT_MULTI);
const [accountId, setAccountId] = useState<string | null>(null);
const [turnout, setTurnout] = useState<Turnout | null>(null);
const [opt10m, setOpt10m] = useState(false);