Alliance unscrupelous accounts & websites (#7917)

This commit is contained in:
Jaco 2022-07-27 11:50:52 +02:00 committed by GitHub
parent c6167b66ec
commit aee78ca152
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 241 additions and 48 deletions

View File

@ -1,22 +1,23 @@
// Copyright 2017-2022 @polkadot/app-alliance authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Member } from './types';
import type { Member, Unscrupelous } 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';
import { useTranslation } from '../translate';
interface Props {
className?: string;
members: Member[];
onClose: () => void;
unscrupelous: Unscrupelous;
}
function Join ({ className, members, onClose }: Props): React.ReactElement<Props> {
function Join ({ className, members, onClose, unscrupelous: { accounts } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { allAccounts } = useAccounts();
@ -27,9 +28,12 @@ function Join ({ className, members, onClose }: Props): React.ReactElement<Props
allAccounts.filter((a) =>
!members.some(({ accountId }) =>
accountId === a
) &&
!accounts.some((accountId) =>
accountId === a
)
),
[allAccounts, members]
[accounts, allAccounts, members]
);
return (

View File

@ -1,7 +1,7 @@
// Copyright 2017-2022 @polkadot/app-alliance authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Member as MemberType } from './types';
import type { Member as MemberType } from '../types';
import React, { useCallback, useContext, useMemo } from 'react';
@ -9,8 +9,8 @@ import { AddressSmall, Badge, Menu, Popup, StatusContext } from '@polkadot/react
import { useAccounts, useApi } from '@polkadot/react-hooks';
import { FormatBalance } from '@polkadot/react-query';
import { useTranslation } from './translate';
import useMemberInfo from './useMemberInfo';
import { useTranslation } from '../translate';
import useMemberInfo from '../useMemberInfo';
interface Props {
className?: string;

View File

@ -1,25 +1,22 @@
// Copyright 2017-2022 @polkadot/app-alliance authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Member as MemberType } from '../types';
import React, { useRef } from 'react';
import { Button, Table } from '@polkadot/react-components';
import { useToggle } from '@polkadot/react-hooks';
import { Table } from '@polkadot/react-components';
import Join from './Join';
import { useTranslation } from '../translate';
import Member from './Member';
import Summary from './Summary';
import { useTranslation } from './translate';
import useMembers from './useMembers';
interface Props {
className?: string;
members?: MemberType[];
}
function Members ({ className }: Props): React.ReactElement<Props> {
function Members ({ className, members }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [isJoinOpen, toggleJoin] = useToggle();
const members = useMembers();
const hdrRef = useRef([
[t<string>('members'), 'start', 2],
@ -30,21 +27,6 @@ function Members ({ 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}

View File

@ -0,0 +1,50 @@
// Copyright 2017-2022 @polkadot/app-alliance authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Unscrupelous } from '../types';
import React from 'react';
import { Button } from '@polkadot/react-components';
import { useToggle } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
import useMembers from '../useMembers';
import Join from './Join';
import Members from './Members';
import Summary from './Summary';
interface Props {
className?: string;
unscrupelous?: Unscrupelous;
}
function Overview ({ className, unscrupelous }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [isJoinOpen, toggleJoin] = useToggle();
const members = useMembers();
return (
<div className={className}>
<Summary />
<Button.Group>
<Button
icon='add'
isDisabled={!members || !unscrupelous}
label={t<string>('Join')}
onClick={toggleJoin}
/>
{members && unscrupelous && isJoinOpen && (
<Join
members={members}
onClose={toggleJoin}
unscrupelous={unscrupelous}
/>
)}
</Button.Group>
<Members members={members} />
</div>
);
}
export default React.memo(Overview);

View File

@ -0,0 +1,23 @@
// Copyright 2017-2022 @polkadot/app-alliance authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { AddressSmall } from '@polkadot/react-components';
interface Props {
className?: string;
value: string;
}
function Account ({ className, value }: Props): React.ReactElement<Props> {
return (
<tr className={className}>
<td className='address all'>
<AddressSmall value={value} />
</td>
</tr>
);
}
export default React.memo(Account);

View File

@ -0,0 +1,21 @@
// Copyright 2017-2022 @polkadot/app-alliance authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
interface Props {
className?: string;
value: string;
}
function Website ({ className, value }: Props): React.ReactElement<Props> {
return (
<tr className={className}>
<td className='start all'>
{value}
</td>
</tr>
);
}
export default React.memo(Website);

View File

@ -0,0 +1,58 @@
// Copyright 2017-2022 @polkadot/app-alliance authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Unscrupelous as UnscrupelousType } from '../types';
import React, { useRef } from 'react';
import { Table } from '@polkadot/react-components';
import { useTranslation } from '../translate';
import Account from './Account';
import Website from './Website';
interface Props {
className?: string;
unscrupelous?: UnscrupelousType;
}
function Unscrupelous ({ className, unscrupelous }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const accRef = useRef([
[t<string>('accounts'), 'start']
]);
const webRef = useRef([
[t<string>('websites'), 'start']
]);
return (
<div className={className}>
<Table
empty={unscrupelous && unscrupelous.accounts && t<string>('No accounts')}
header={accRef.current}
>
{unscrupelous && unscrupelous.accounts.map((m) => (
<Account
key={m}
value={m}
/>
))}
</Table>
<Table
empty={unscrupelous && unscrupelous.websites && t<string>('No websites')}
header={webRef.current}
>
{unscrupelous && unscrupelous.websites.map((m) => (
<Website
key={m}
value={m}
/>
))}
</Table>
</div>
);
}
export default React.memo(Unscrupelous);

View File

@ -2,11 +2,14 @@
// SPDX-License-Identifier: Apache-2.0
import React, { useRef } from 'react';
import { Route, Switch } from 'react-router';
import { Tabs } from '@polkadot/react-components';
import Members from './Members';
import Overview from './Overview';
import { useTranslation } from './translate';
import Unscrupelous from './Unscrupelous';
import useUnscrupelous from './useUnscrupelous';
interface Props {
basePath: string;
@ -15,12 +18,17 @@ interface Props {
function AllianceApp ({ basePath, className }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const unscrupelous = useUnscrupelous();
const itemsRef = useRef([
{
isRoot: true,
name: 'index',
name: 'overview',
text: t<string>('Overview')
},
{
name: 'unscrupelous',
text: t<string>('Unscrupelous')
}
]);
@ -30,7 +38,14 @@ function AllianceApp ({ basePath, className }: Props): React.ReactElement<Props>
basePath={basePath}
items={itemsRef.current}
/>
<Members />
<Switch>
<Route path={`${basePath}/unscrupelous`}>
<Unscrupelous unscrupelous={unscrupelous} />
</Route>
<Route>
<Overview unscrupelous={unscrupelous} />
</Route>
</Switch>
</main>
);
}

View File

@ -14,3 +14,8 @@ export interface MemberInfo {
deposit?: BN | null;
isUpForKicking: boolean;
}
export interface Unscrupelous {
accounts: string[];
websites: string[];
}

View File

@ -13,17 +13,17 @@ function useMemberInfoImpl (accountId: string): MemberInfo | undefined {
const upForKicking = useCall<bool>(api.query.alliance.upForKicking, [accountId]);
const depositOf = useCall<Option<UInt>>(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]);
return useMemo(
(): MemberInfo | undefined =>
upForKicking && depositOf
? {
accountId,
deposit: depositOf.unwrapOr(null),
isUpForKicking: upForKicking.isTrue
}
: undefined,
[accountId, upForKicking, depositOf]
);
}
export default createNamedHook('useMemberInfo', useMemberInfoImpl);

View File

@ -10,7 +10,7 @@ import { createNamedHook, useApi, useCallMulti } from '@polkadot/react-hooks';
const ROLES = <const> ['Founder', 'Fellow', 'Ally'];
function addMembers (prev: Member[] = [], query: AccountId32[][]): Member[] {
function addMembers (prev: Member[], query: AccountId32[][]): Member[] {
const all: Member[] = [];
for (let i = 0; i < ROLES.length; i++) {
@ -19,7 +19,10 @@ function addMembers (prev: Member[] = [], query: AccountId32[][]): Member[] {
for (let j = 0; j < accountIds.length; j++) {
const accountId = accountIds[j].toString();
const existing = prev.find((p) => p.accountId === accountId && p.role === role);
const existing = prev.find((p) =>
p.accountId === accountId &&
p.role === role
);
all.push(existing || {
accountId,
@ -42,7 +45,7 @@ function useMembersImpl (): Member[] | undefined {
useEffect((): void => {
query && query.length === ROLES.length &&
setState((prev) => addMembers(prev, query));
setState((prev = []) => addMembers(prev, query));
}, [query]);
return state;

View File

@ -0,0 +1,32 @@
// Copyright 2017-2022 @polkadot/app-alliance authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { Bytes } from '@polkadot/types';
import type { AccountId32 } from '@polkadot/types/interfaces';
import type { Unscrupelous } from './types';
import { useMemo } from 'react';
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
function getUnscrupelous (accounts: AccountId32[], websites: Bytes[]): Unscrupelous {
return {
accounts: accounts.map((a) => a.toString()),
websites: websites.filter((b) => b.isAscii).map((b) => b.toString())
};
}
function useUnscrupelousImpl (): Unscrupelous | undefined {
const { api } = useApi();
const accounts = useCall<AccountId32[]>(api.query.alliance.unscrupulousAccounts, []);
const websites = useCall<Bytes[]>(api.query.alliance.unscrupulousWebsites, []);
return useMemo(
(): Unscrupelous | undefined =>
accounts && websites &&
getUnscrupelous(accounts, websites),
[accounts, websites]
);
}
export default createNamedHook('useUnscrupelous', useUnscrupelousImpl);