Browse Source

Members dropdown

Leszek Wiesner 4 years ago
parent
commit
d3987c62b5

+ 8 - 10
packages/joy-election/src/VoteForm.tsx

@@ -8,17 +8,18 @@ import { AppProps, I18nProps } from '@polkadot/react-components/types';
 import { ApiProps } from '@polkadot/react-api/types';
 import { withCalls, withMulti } from '@polkadot/react-api/with';
 import { AccountId, Balance } from '@polkadot/types/interfaces';
-import { Button, Input, Labelled, InputAddress } from '@polkadot/react-components/index';
+import { Button, Input, Labelled } from '@polkadot/react-components/index';
 import { SubmittableResult } from '@polkadot/api';
 import { formatBalance } from '@polkadot/util';
 
 import translate from './translate';
-import { accountIdsToOptions, hashVote } from './utils';
+import { hashVote } from './utils';
 import { queryToProp, ZERO, getUrlParam, nonEmptyStr } from '@polkadot/joy-utils/index';
 import TxButton from '@polkadot/joy-utils/TxButton';
 import InputStake from '@polkadot/joy-utils/InputStake';
 import CandidatePreview from "./CandidatePreview";
 import { MyAccountProps, withOnlyMembers } from '@polkadot/joy-utils/MyAccount';
+import MembersDropdown from "@polkadot/joy-utils/MembersDropdown";
 import { saveVote, NewVote } from './myVotesStore';
 import { TxFailedCallback } from '@polkadot/react-components/Status/types';
 
@@ -63,7 +64,6 @@ class Component extends React.PureComponent<Props, State> {
     const { myAddress } = this.props;
     const { applicantId, stake, salt, isStakeValid, isFormSubmitted } = this.state;
     const isFormValid = nonEmptyStr(applicantId) && isStakeValid;
-    const applicantOpts = accountIdsToOptions(this.props.applicants || []);
     const hashedVote = hashVote(applicantId, salt);
 
     const buildNewVote = (): Partial<NewVote> => ({
@@ -117,13 +117,11 @@ class Component extends React.PureComponent<Props, State> {
       // New vote form:
       : <div>
         <div className='ui--row'>
-          <InputAddress
-            label='Applicant to vote for:'
-            onChange={this.onChangeApplicant}
-            type='address'
-            options={applicantOpts}
-            value={applicantId}
-            placeholder='Select an applicant you support'
+          <MembersDropdown
+            onChange={ (event, data) => this.onChangeApplicant(data.value as string) }
+            accounts={this.props.applicants || []}
+            value={applicantId || ''}
+            placeholder="Select an applicant you support"
           />
         </div>
         <InputStake

+ 3 - 15
packages/joy-utils/src/MemberByAccountPreview.tsx

@@ -1,25 +1,13 @@
 import React, { useEffect, useState, useContext } from "react";
 
 import { Loader } from 'semantic-ui-react';
-import { ApiPromise } from "@polkadot/api";
 import { ApiContext } from "@polkadot/react-api";
 import ProfilePreview from "./MemberProfilePreview";
-import { MemberId, Profile } from "@joystream/types/members";
-import { Option } from "@polkadot/types";
 import { AccountId } from "@polkadot/types/interfaces";
-import { Vec } from "@polkadot/types/codec";
+import { memberFromAccount, MemberFromAccount } from "./accounts";
 
 import styled from 'styled-components';
 
-const memberFromAccount = async (api: ApiPromise, accountId: AccountId | string) => {
-  const [memberId] =
-    ((await api.query.members.memberIdsByRootAccountId(accountId)) as Vec<MemberId>)
-    .concat((await api.query.members.memberIdsByControllerAccountId(accountId)) as Vec<MemberId>);
-  const member = (await api.query.members.memberProfile(memberId)) as Option<Profile>;
-
-  return { id: memberId, profile: member.unwrapOr(undefined) };
-}
-
 const MemberByAccount = styled.div``;
 
 type Props = {
@@ -28,7 +16,7 @@ type Props = {
 
 const MemberByAccountPreview: React.FunctionComponent<Props> = ({ accountId }) => {
   const { api } = useContext(ApiContext);
-  const [ member, setMember ] = useState(null as { id: MemberId, profile?: Profile } | null);
+  const [ member, setMember ] = useState(null as MemberFromAccount | null);
   useEffect(
     () => { memberFromAccount(api, accountId).then(member => setMember(member)); },
     [accountId]
@@ -43,7 +31,7 @@ const MemberByAccountPreview: React.FunctionComponent<Props> = ({ accountId }) =
               avatar_uri={member.profile.avatar_uri.toString()}
               root_account={member.profile.root_account.toString()}
               handle={member.profile.handle.toString()}
-              id={member.id.toNumber()}
+              id={member.id}
               link={true}/>
             : 'Member profile not found!'
         )

+ 63 - 0
packages/joy-utils/src/MembersDropdown.tsx

@@ -0,0 +1,63 @@
+import React, { useEffect, useState, useContext } from "react";
+import { Dropdown, DropdownItemProps, DropdownProps } from "semantic-ui-react";
+import { Profile } from "@joystream/types/members";
+import { memberFromAccount, MemberFromAccount } from "./accounts";
+import { AccountId } from "@polkadot/types/interfaces";
+import { ApiContext } from "@polkadot/react-api";
+import styled from 'styled-components';
+
+const StyledMembersDropdown = styled(Dropdown)`
+  & .ui.avatar.image {
+    width: 2em !important;
+    height: 2em !important;
+  }
+`;
+
+function membersToOptions(members: MemberFromAccount[]) {
+  const validMembers = members.filter(m => m.profile !== undefined) as (MemberFromAccount & { profile: Profile })[];
+  return validMembers
+    .map(({ id, profile, account }) => ({
+      key: profile.handle,
+      text: `${ profile.handle } (id:${ id })`,
+      value: account,
+      image: profile.avatar_uri.toString() ? { avatar: true, src: profile.avatar_uri } : null
+    }));
+}
+
+type Props = {
+  accounts: (string | AccountId)[],
+  name?: DropdownProps["name"],
+  onChange?: DropdownProps["onChange"],
+  value?: DropdownProps["value"],
+  placeholder?: DropdownProps["placeholder"],
+};
+
+const MembersDropdown: React.FunctionComponent<Props> = ({ accounts, ...passedProps }) => {
+  const { api } = useContext(ApiContext);
+  // State
+  const [ loading, setLoading ] = useState(true);
+  const [ membersOptions, setMembersOptions ] = useState([] as DropdownItemProps[]);
+  // Generate members options array on load
+  useEffect(() => {
+    Promise
+      .all(accounts.map(acc => memberFromAccount(api, acc)))
+      .then(members => {
+        setMembersOptions(membersToOptions(members));
+        setLoading(false);
+      });
+  }, [accounts]);
+
+  return (
+    <StyledMembersDropdown
+      clearable
+      search
+      fluid
+      selection
+      { ...passedProps }
+      options={membersOptions}
+      loading={loading}
+    />
+  );
+};
+
+export default MembersDropdown;

+ 21 - 0
packages/joy-utils/src/accounts.ts

@@ -9,6 +9,12 @@ import keyring from '@polkadot/ui-keyring';
 import { isHex, u8aToHex } from '@polkadot/util';
 import { keyExtractSuri, mnemonicGenerate, mnemonicValidate, randomAsU8a } from '@polkadot/util-crypto';
 
+import { ApiPromise } from "@polkadot/api";
+import { MemberId, Profile } from "@joystream/types/members";
+import { Option } from "@polkadot/types";
+import { AccountId } from "@polkadot/types/interfaces";
+import { Vec } from "@polkadot/types/codec";
+
 export type SeedType = 'bip' | 'raw' | 'dev';
 
 export interface AddressState {
@@ -135,3 +141,18 @@ export function createAccount (suri: string, pairType: KeypairType, name: string
 export function isPasswordValid(password: string): boolean {
   return password.length == 0 || keyring.isPassValid(password)
 }
+
+export type MemberFromAccount = { account: string, id: number; profile?: Profile };
+
+export async function memberFromAccount(api: ApiPromise, accountId: AccountId | string): Promise<MemberFromAccount> {
+  const [memberId] =
+    ((await api.query.members.memberIdsByRootAccountId(accountId)) as Vec<MemberId>)
+    .concat((await api.query.members.memberIdsByControllerAccountId(accountId)) as Vec<MemberId>);
+  const member = (await api.query.members.memberProfile(memberId)) as Option<Profile>;
+
+  return {
+    account: accountId.toString(),
+    id: memberId.toNumber(),
+    profile: member.unwrapOr(undefined)
+  };
+}