Details.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import React from 'react';
  2. import { Link } from 'react-router-dom';
  3. import { Table, Loader } from 'semantic-ui-react';
  4. import ReactMarkdown from 'react-markdown';
  5. import { IdentityIcon } from '@polkadot/react-components';
  6. import { ApiProps } from '@polkadot/react-api/types';
  7. import { I18nProps } from '@polkadot/react-components/types';
  8. import { withCalls } from '@polkadot/react-api/with';
  9. import { Option } from '@polkadot/types';
  10. import BalanceDisplay from '@polkadot/react-components/Balance';
  11. import AddressMini from '@polkadot/react-components/AddressMiniJoy';
  12. import { formatNumber } from '@polkadot/util';
  13. import translate from './translate';
  14. import { MemberId, Membership, EntryMethod, Paid, Screening, Genesis, SubscriptionId } from '@joystream/types/members';
  15. import { queryMembershipToProp } from './utils';
  16. import { Seat } from '@joystream/types/council';
  17. import { nonEmptyStr, queryToProp } from '@polkadot/joy-utils/index';
  18. import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/MyAccount';
  19. type Props = ApiProps & I18nProps & MyAccountProps & {
  20. preview?: boolean;
  21. memberId: MemberId;
  22. membership?: Membership;
  23. activeCouncil?: Seat[];
  24. };
  25. class Component extends React.PureComponent<Props> {
  26. render () {
  27. const { membership } = this.props;
  28. return membership && !membership.handle.isEmpty
  29. ? this.renderProfile(membership)
  30. : (
  31. <div className={'item ProfileDetails'}>
  32. <Loader active inline/>
  33. </div>
  34. );
  35. }
  36. private renderProfile (membership: Membership) {
  37. const {
  38. preview = false,
  39. myAddress,
  40. activeCouncil = []
  41. } = this.props;
  42. const {
  43. handle,
  44. avatar_uri,
  45. root_account,
  46. controller_account
  47. } = membership;
  48. const hasAvatar = avatar_uri && nonEmptyStr(avatar_uri.toString());
  49. const isMyProfile = myAddress && (myAddress === root_account.toString() || myAddress === controller_account.toString());
  50. const isCouncilor: boolean = (
  51. (activeCouncil.find(x => root_account.eq(x.member)) !== undefined) ||
  52. (activeCouncil.find(x => controller_account.eq(x.member)) !== undefined)
  53. );
  54. return (
  55. <>
  56. <div className={`item ProfileDetails ${isMyProfile && 'MyProfile'}`}>
  57. {hasAvatar
  58. ? <img className='ui avatar image' src={avatar_uri.toString()} />
  59. : <IdentityIcon className='image' value={root_account} size={40} />
  60. }
  61. <div className='content'>
  62. <div className='header'>
  63. <Link to={`/members/${handle.toString()}`} className='handle'>{handle.toString()}</Link>
  64. {isMyProfile && <Link to={'/members/edit'} className='ui tiny button'>Edit my profile</Link>}
  65. </div>
  66. <div className='description'>
  67. {isCouncilor &&
  68. <b className='muted text' style={{ color: '#607d8b' }}>
  69. <i className='university icon'></i>
  70. Council member
  71. </b>}
  72. <BalanceDisplay label='Balance(root): ' params={root_account} />
  73. <div>MemberId: {this.props.memberId.toString()}</div>
  74. </div>
  75. </div>
  76. </div>
  77. {!preview && this.renderDetails(membership, isCouncilor)}
  78. </>
  79. );
  80. }
  81. private renderDetails (membership: Membership, isCouncilor: boolean) {
  82. const {
  83. about,
  84. registered_at_block,
  85. registered_at_time,
  86. entry,
  87. suspended,
  88. subscription,
  89. root_account,
  90. controller_account
  91. } = membership;
  92. const { memberId } = this.props;
  93. return (
  94. <Table celled selectable compact definition className='ProfileDetailsTable'>
  95. <Table.Body>
  96. <Table.Row>
  97. <Table.Cell>Membership ID</Table.Cell>
  98. <Table.Cell>{memberId.toNumber()}</Table.Cell>
  99. </Table.Row>
  100. <Table.Row>
  101. <Table.Cell>Root account</Table.Cell>
  102. <Table.Cell><AddressMini value={root_account} isShort={false} isPadded={false} withBalance /></Table.Cell>
  103. </Table.Row>
  104. <Table.Row>
  105. <Table.Cell>Controller account</Table.Cell>
  106. <Table.Cell><AddressMini value={controller_account} isShort={false} isPadded={false} withBalance /></Table.Cell>
  107. </Table.Row>
  108. <Table.Row>
  109. <Table.Cell>Registered on</Table.Cell>
  110. <Table.Cell>{new Date(registered_at_time.toNumber()).toLocaleString()} at block #{formatNumber(registered_at_block)}</Table.Cell>
  111. </Table.Row>
  112. <Table.Row>
  113. <Table.Cell>Suspended?</Table.Cell>
  114. <Table.Cell>{suspended.eq(true) ? 'Yes' : 'No'}</Table.Cell>
  115. </Table.Row>
  116. <Table.Row>
  117. <Table.Cell>Council member?</Table.Cell>
  118. <Table.Cell>{isCouncilor ? 'Yes' : 'No'}</Table.Cell>
  119. </Table.Row>
  120. <Table.Row>
  121. <Table.Cell>Entry method</Table.Cell>
  122. <Table.Cell>{this.renderEntryMethod(entry)}</Table.Cell>
  123. </Table.Row>
  124. <Table.Row>
  125. <Table.Cell>Subscription ID</Table.Cell>
  126. <Table.Cell>{this.renderSubscription(subscription)}</Table.Cell>
  127. </Table.Row>
  128. <Table.Row>
  129. <Table.Cell>About</Table.Cell>
  130. <Table.Cell><ReactMarkdown className='JoyMemo--full' source={about.toString()} linkTarget='_blank' /></Table.Cell>
  131. </Table.Row>
  132. </Table.Body>
  133. </Table>
  134. );
  135. }
  136. private renderEntryMethod (entry: EntryMethod) {
  137. const etype = entry.type;
  138. if (etype === Paid.name) {
  139. const paid = entry.value as Paid;
  140. return <div>Paid, terms ID: {paid.toNumber()}</div>;
  141. } else if (etype === Screening.name) {
  142. const accountId = entry.value as Screening;
  143. return <div>Screened by <AddressMini value={accountId} isShort={false} isPadded={false} withBalance /></div>;
  144. } else if (etype === Genesis.name) {
  145. return <div>Created at Genesis</div>;
  146. } else {
  147. return <em className='muted text'>Unknown</em>;
  148. }
  149. }
  150. private renderSubscription (subscription: Option<SubscriptionId>) {
  151. return subscription.isNone
  152. ? <em className='muted text'>No subscription yet.</em>
  153. : subscription.value.toString();
  154. }
  155. }
  156. export default translate(withMyAccount(
  157. withCalls<Props>(
  158. queryToProp('query.council.activeCouncil'),
  159. queryMembershipToProp(
  160. 'membershipById',
  161. { paramName: 'memberId', propName: 'membership' }
  162. )
  163. )(Component)
  164. ));