Pārlūkot izejas kodu

Stop hiding information from people without a key

deam 4 gadi atpakaļ
vecāks
revīzija
7f4eb180ce

+ 34 - 33
packages/app-staking/src/Actions/Accounts.tsx

@@ -8,6 +8,7 @@ import { I18nProps } from '@polkadot/react-components/types';
 import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
 import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
 import { withCalls, withMulti } from '@polkadot/react-api/with';
+import { MyAddressProps, withOnlyMembers } from '@polkadot/joy-utils/MyAccount';
 
 import React, { useState } from 'react';
 import styled from 'styled-components';
@@ -23,7 +24,7 @@ interface Props extends I18nProps, ComponentProps, ApiProps {
   myControllers?: string[];
 }
 
-function getMyStashes (myControllers?: string[], allAccounts?: SubjectInfo): string[] | null {
+function getMyStashes(myControllers?: string[], allAccounts?: SubjectInfo): string[] | null {
   const result: string[] = [];
 
   if (!myControllers) {
@@ -39,11 +40,18 @@ function getMyStashes (myControllers?: string[], allAccounts?: SubjectInfo): str
   return result;
 }
 
-function Accounts ({ allAccounts, allStashes, className, myControllers, recentlyOnline, t }: Props): React.ReactElement<Props> {
+function Accounts({
+  allAccounts,
+  allStashes,
+  className,
+  myControllers,
+  recentlyOnline,
+  t
+}: Props): React.ReactElement<Props> {
   const [isNewStakeOpen, setIsNewStateOpen] = useState(false);
   const myStashes = getMyStashes(myControllers, allAccounts);
-  const stashOptions = allStashes.map((stashId): KeyringSectionOption =>
-    createOption(stashId, (<AccountName params={stashId} />) as any)
+  const stashOptions = allStashes.map(
+    (stashId): KeyringSectionOption => createOption(stashId, (<AccountName params={stashId} />) as any)
   );
   const isEmpty = !isNewStakeOpen && (!myStashes || myStashes.length === 0);
 
@@ -51,51 +59,44 @@ function Accounts ({ allAccounts, allStashes, className, myControllers, recently
 
   return (
     <CardGrid
-      buttons={
-        <Button
-          isPrimary
-          key='new-stake'
-          label={t('New stake')}
-          icon='add'
-          onClick={_toggleNewStake}
-        />
-      }
+      buttons={<Button isPrimary key="new-stake" label={t('New stake')} icon="add" onClick={_toggleNewStake} />}
       className={className}
       emptyText={t('No funds staked yet.')}
       isEmpty={isEmpty}
     >
-      {isNewStakeOpen && (
-        <StartStaking onClose={_toggleNewStake} />
-      )}
-      {myStashes && myStashes.map((address, index): React.ReactNode => (
-        address && (
-          <Account
-            allStashes={allStashes}
-            accountId={address}
-            key={index}
-            recentlyOnline={recentlyOnline}
-            stashOptions={stashOptions}
-          />
-        )
-      ))}
+      {isNewStakeOpen && <StartStaking onClose={_toggleNewStake} />}
+      {myStashes &&
+        myStashes.map(
+          (address, index): React.ReactNode =>
+            address && (
+              <Account
+                allStashes={allStashes}
+                accountId={address}
+                key={index}
+                recentlyOnline={recentlyOnline}
+                stashOptions={stashOptions}
+              />
+            )
+        )}
     </CardGrid>
   );
 }
 
 export default withMulti(
-  styled(Accounts)`
+  withOnlyMembers(styled(Accounts)`
     .ui--CardGrid-buttons {
       text-align: right;
     }
-  `,
+  `),
   translate,
-  withCalls<Props>(
-    ['query.staking.bonded', {
+  withCalls<Props>([
+    'query.staking.bonded',
+    {
       isMulti: true,
       paramPick: ({ allAccounts }: Props): undefined | string[] => {
         return allAccounts && Object.keys(allAccounts);
       },
       propName: 'myControllers'
-    }]
-  )
+    }
+  ])
 );

+ 48 - 37
packages/app-staking/src/Overview/CurrentList.tsx

@@ -23,40 +23,52 @@ interface Props extends I18nProps {
   stakingOverview?: DerivedStakingOverview;
 }
 
-function renderColumn (myAccounts: string[], addresses: AccountId[] | string[], defaultName: string, withOnline: boolean, filter: string, { authorsMap, lastAuthors, recentlyOnline, stakingOverview }: Props, pointIndexes?: number[]): React.ReactNode {
-  return (addresses as AccountId[]).map((address, index): React.ReactNode => (
-    <Address
-      address={address}
-      authorsMap={authorsMap}
-      defaultName={defaultName}
-      filter={filter}
-      isElected={stakingOverview && stakingOverview.currentElected.some((accountId): boolean => accountId.eq(address))}
-      lastAuthors={lastAuthors}
-      key={address.toString()}
-      myAccounts={myAccounts}
-      points={
-        stakingOverview && pointIndexes && pointIndexes[index] !== -1
-          ? stakingOverview.eraPoints.individual[pointIndexes[index]]
-          : undefined
-      }
-      recentlyOnline={
-        withOnline
-          ? recentlyOnline
-          : undefined
-      }
-    />
-  ));
+function renderColumn(
+  myAccounts: string[],
+  addresses: AccountId[] | string[],
+  defaultName: string,
+  withOnline: boolean,
+  filter: string,
+  { authorsMap, lastAuthors, recentlyOnline, stakingOverview }: Props,
+  pointIndexes?: number[]
+): React.ReactNode {
+  return (addresses as AccountId[]).map(
+    (address, index): React.ReactNode => (
+      <Address
+        address={address}
+        authorsMap={authorsMap}
+        defaultName={defaultName}
+        filter={filter}
+        isElected={
+          stakingOverview && stakingOverview.currentElected.some((accountId): boolean => accountId.eq(address))
+        }
+        lastAuthors={lastAuthors}
+        key={address.toString()}
+        myAccounts={myAccounts}
+        points={
+          stakingOverview && pointIndexes && pointIndexes[index] !== -1
+            ? stakingOverview.eraPoints.individual[pointIndexes[index]]
+            : undefined
+        }
+        recentlyOnline={withOnline ? recentlyOnline : undefined}
+      />
+    )
+  );
 }
 
-function filterAccounts (list: string[] = [], without: AccountId[] | string[]): string[] {
+function filterAccounts(list: string[] = [], without: AccountId[] | string[]): string[] {
   return list.filter((accountId): boolean => !without.includes(accountId as any));
 }
 
-function CurrentList (props: Props): React.ReactElement<Props> {
+function CurrentList(props: Props): React.ReactElement<Props> {
   const { isSubstrateV2 } = useContext(ApiContext);
   const [filter, setFilter] = useState<ValidatorFilter>('all');
   const [myAccounts] = useState(keyring.getAccounts().map(({ address }): string => address));
-  const [{ electedFiltered, nextFiltered, pointIndexes }, setFiltered] = useState<{ electedFiltered: string[]; nextFiltered: string[]; pointIndexes: number[] }>({ electedFiltered: [], nextFiltered: [], pointIndexes: [] });
+  const [{ electedFiltered, nextFiltered, pointIndexes }, setFiltered] = useState<{
+    electedFiltered: string[];
+    nextFiltered: string[];
+    pointIndexes: number[];
+  }>({ electedFiltered: [], nextFiltered: [], pointIndexes: [] });
   const { next, stakingOverview, t } = props;
 
   useEffect((): void => {
@@ -73,7 +85,11 @@ function CurrentList (props: Props): React.ReactElement<Props> {
 
   return (
     <div>
-      <FilterOverlay>
+      <FilterOverlay
+        style={{
+          top: myAccounts.length ? '5.5rem' : '5px'
+        }}
+      >
         <Dropdown
           onChange={setFilter}
           options={[
@@ -89,17 +105,12 @@ function CurrentList (props: Props): React.ReactElement<Props> {
           withLabel={false}
         />
       </FilterOverlay>
-      <Columar className='validator--ValidatorsList'>
-        <Column
-          emptyText={t('No addresses found')}
-          headerText={t('validators')}
-        >
-          {stakingOverview && renderColumn(myAccounts, stakingOverview.validators, t('validator'), true, filter, props, pointIndexes)}
+      <Columar className="validator--ValidatorsList">
+        <Column emptyText={t('No addresses found')} headerText={t('validators')}>
+          {stakingOverview &&
+            renderColumn(myAccounts, stakingOverview.validators, t('validator'), true, filter, props, pointIndexes)}
         </Column>
-        <Column
-          emptyText={t('No addresses found')}
-          headerText={t('next up')}
-        >
+        <Column emptyText={t('No addresses found')} headerText={t('next up')}>
           {(electedFiltered.length !== 0 || nextFiltered.length !== 0) && (
             <>
               {renderColumn(myAccounts, electedFiltered, t('intention'), false, filter, props)}

+ 15 - 9
packages/app-staking/src/Overview/index.tsx

@@ -14,24 +14,30 @@ import Summary from './Summary';
 
 interface Props extends BareProps, ComponentProps {}
 
-export default function Overview ({ allControllers, allStashes, recentlyOnline, stakingOverview }: Props): React.ReactElement<Props> {
+export default function Overview({
+  allControllers,
+  allStashes,
+  recentlyOnline,
+  stakingOverview
+}: Props): React.ReactElement<Props> {
   const { isSubstrateV2 } = useContext(ApiContext);
   const { byAuthor, lastBlockAuthors, lastBlockNumber } = useContext(BlockAuthorsContext);
   const [next, setNext] = useState<string[]>([]);
   const validators = stakingOverview && stakingOverview.validators;
 
   useEffect((): void => {
-    validators && setNext(
-      isSubstrateV2
-        // this is a V2 node currentValidators is a list of stashes
-        ? allStashes.filter((address): boolean => !validators.includes(address as any))
-        // this is a V1 node currentValidators is a list of controllers
-        : allControllers.filter((address): boolean => !validators.includes(address as any))
-    );
+    validators &&
+      setNext(
+        isSubstrateV2
+          ? // this is a V2 node currentValidators is a list of stashes
+            allStashes.filter((address): boolean => !validators.includes(address as any))
+          : // this is a V1 node currentValidators is a list of controllers
+            allControllers.filter((address): boolean => !validators.includes(address as any))
+      );
   }, [allControllers, allStashes, validators]);
 
   return (
-    <div className='staking--Overview'>
+    <div className="staking--Overview">
       <Summary
         allControllers={allControllers}
         lastBlock={lastBlockNumber}

+ 28 - 17
packages/app-staking/src/index.tsx

@@ -34,8 +34,16 @@ interface Props extends AppProps, ApiProps, I18nProps {
 const EMPY_ACCOUNTS: string[] = [];
 const EMPTY_ALL: [string[], string[]] = [EMPY_ACCOUNTS, EMPY_ACCOUNTS];
 
-function App ({ allAccounts, allStashesAndControllers: [allStashes, allControllers] = EMPTY_ALL, basePath, className, recentlyOnline, stakingOverview, t }: Props): React.ReactElement<Props> {
-  const _renderComponent = (Component: React.ComponentType<ComponentProps>): () => React.ReactNode => {
+function App({
+  allAccounts,
+  allStashesAndControllers: [allStashes, allControllers] = EMPTY_ALL,
+  basePath,
+  className,
+  recentlyOnline,
+  stakingOverview,
+  t
+}: Props): React.ReactElement<Props> {
+  const _renderComponent = (Component: React.ComponentType<ComponentProps>): (() => React.ReactNode) => {
     // eslint-disable-next-line react/display-name
     return (): React.ReactNode => {
       if (!allAccounts) {
@@ -54,17 +62,19 @@ function App ({ allAccounts, allStashesAndControllers: [allStashes, allControlle
     };
   };
 
+  const hasAccount = allAccounts && Object.keys(allAccounts).length;
+
   return (
     <main className={`staking--App ${className}`}>
-      <HelpOverlay md={basicMd} />
+      <HelpOverlay
+        md={basicMd}
+        style={{
+          top: hasAccount ? '5.25rem' : 0
+        }}
+      />
       <header>
         <Tabs
           basePath={basePath}
-          hidden={
-            !allAccounts || Object.keys(allAccounts).length === 0
-              ? ['actions']
-              : []
-          }
           items={[
             {
               isRoot: true,
@@ -95,15 +105,16 @@ export default withMulti(
   translate,
   withCalls<Props>(
     ['derive.imOnline.receivedHeartbeats', { propName: 'recentlyOnline' }],
-    ['derive.staking.controllers', {
-      propName: 'allStashesAndControllers',
-      transform: ([stashes, controllers]: [AccountId[], Option<AccountId>[]]): [string[], string[]] => [
-        stashes.map((accountId): string => accountId.toString()),
-        controllers
-          .filter((optId): boolean => optId.isSome)
-          .map((accountId): string => accountId.unwrap().toString())
-      ]
-    }],
+    [
+      'derive.staking.controllers',
+      {
+        propName: 'allStashesAndControllers',
+        transform: ([stashes, controllers]: [AccountId[], Option<AccountId>[]]): [string[], string[]] => [
+          stashes.map((accountId): string => accountId.toString()),
+          controllers.filter((optId): boolean => optId.isSome).map((accountId): string => accountId.unwrap().toString())
+        ]
+      }
+    ],
     ['derive.staking.overview', { propName: 'stakingOverview' }]
   ),
   withObservable(accountObservable.subject, { propName: 'allAccounts' })

+ 50 - 50
packages/apps/src/SideBar/Item.tsx

@@ -23,7 +23,6 @@ import { queryToProp } from '@polkadot/joy-utils/index';
 import { ElectionStage } from '@joystream/types/';
 import { councilSidebarName } from '@polkadot/apps-routing/joy-election';
 
-
 interface Props extends I18nProps {
   isCollapsed: boolean;
   onClick: () => void;
@@ -34,15 +33,14 @@ interface Props extends I18nProps {
 }
 
 type Subtitle = {
-  text: string,
-  classes: string[]
+  text: string;
+  classes: string[];
 };
 
-
 const disabledLog: Map<string, string> = new Map();
 const TOOLTIP_OFFSET = { right: -4 };
 
-function logDisabled (route: string, message: string): void {
+function logDisabled(route: string, message: string): void {
   if (!disabledLog.get(route)) {
     disabledLog.set(route, message);
 
@@ -50,7 +48,7 @@ function logDisabled (route: string, message: string): void {
   }
 }
 
-function hasEndpoint (api: ApiPromise, endpoint: string): boolean {
+function hasEndpoint(api: ApiPromise, endpoint: string): boolean {
   const [area, section, method] = endpoint.split('.');
 
   try {
@@ -60,11 +58,15 @@ function hasEndpoint (api: ApiPromise, endpoint: string): boolean {
   }
 }
 
-function checkVisible (name: string, { api, isApiReady, isApiConnected }: ApiProps, hasAccounts: boolean, hasSudo: boolean, { isHidden, needsAccounts, needsApi, needsSudo }: Route['display']): boolean {
+function checkVisible(
+  name: string,
+  { api, isApiReady, isApiConnected }: ApiProps,
+  hasAccounts: boolean,
+  hasSudo: boolean,
+  { isHidden, needsAccounts, needsApi, needsSudo }: Route['display']
+): boolean {
   if (isHidden) {
     return false;
-  } else if (needsAccounts && !hasAccounts) {
-    return false;
   } else if (!needsApi) {
     return true;
   } else if (!isApiReady || !isApiConnected) {
@@ -89,7 +91,15 @@ function checkVisible (name: string, { api, isApiReady, isApiConnected }: ApiPro
   return notFound.length === 0;
 }
 
-function Item ({ allAccounts, route: { Modal, display, i18n, icon, name }, t, isCollapsed, onClick, sudoKey, electionStage }: Props): React.ReactElement<Props> | null {
+function Item({
+  allAccounts,
+  route: { Modal, display, i18n, icon, name },
+  t,
+  isCollapsed,
+  onClick,
+  sudoKey,
+  electionStage
+}: Props): React.ReactElement<Props> | null {
   const apiProps = useContext(ApiContext);
   const [hasAccounts, setHasAccounts] = useState(false);
   const [hasSudo, setHasSudo] = useState(false);
@@ -126,54 +136,46 @@ function Item ({ allAccounts, route: { Modal, display, i18n, icon, name }, t, is
       }
     }
     return undefined;
-  }
+  };
 
   const subtitle = _getSubtitle(name);
 
   const body = (
     <>
       <Icon name={icon} />
-      <span className='text SidebarItem'>
-            <div>{t(`sidebar.${name}`, i18n)}</div>
-            {subtitle && <div className={`SidebarSubtitle ${subtitle.classes.join(' ')}`}>{subtitle.text}</div>}
+      <span className="text SidebarItem">
+        <div>{t(`sidebar.${name}`, i18n)}</div>
+        {subtitle && <div className={`SidebarSubtitle ${subtitle.classes.join(' ')}`}>{subtitle.text}</div>}
       </span>
-      <Tooltip
-        offset={TOOLTIP_OFFSET}
-        place='right'
-        text={t(`sidebar.${name}`, i18n)}
-        trigger={`nav-${name}`}
-      />
+      <Tooltip offset={TOOLTIP_OFFSET} place="right" text={t(`sidebar.${name}`, i18n)} trigger={`nav-${name}`} />
     </>
   );
 
   return (
-    <Menu.Item className='apps--SideBar-Item'>
-      {Modal
-        ? (
-          <a
-            className='apps--SideBar-Item-NavLink'
-            data-for={`nav-${name}`}
-            data-tip
-            data-tip-disable={!isCollapsed}
-            onClick={onClick}
-          >
-            {body}
-          </a>
-        )
-        : (
-          <NavLink
-            activeClassName='apps--SideBar-Item-NavLink-active'
-            className='apps--SideBar-Item-NavLink'
-            data-for={`nav-${name}`}
-            data-tip
-            data-tip-disable={!isCollapsed}
-            onClick={onClick}
-            to={`/${name}`}
-          >
-            {body}
-          </NavLink>
-        )
-      }
+    <Menu.Item className="apps--SideBar-Item">
+      {Modal ? (
+        <a
+          className="apps--SideBar-Item-NavLink"
+          data-for={`nav-${name}`}
+          data-tip
+          data-tip-disable={!isCollapsed}
+          onClick={onClick}
+        >
+          {body}
+        </a>
+      ) : (
+        <NavLink
+          activeClassName="apps--SideBar-Item-NavLink-active"
+          className="apps--SideBar-Item-NavLink"
+          data-for={`nav-${name}`}
+          data-tip
+          data-tip-disable={!isCollapsed}
+          onClick={onClick}
+          to={`/${name}`}
+        >
+          {body}
+        </NavLink>
+      )}
     </Menu.Item>
   );
 }
@@ -182,8 +184,6 @@ export default withMulti(
   Item,
   translate,
   withCalls(queryToProp('query.councilElection.stage', { propName: 'electionStage' })),
-  withCalls<Props>(
-    ['query.sudo.key', { propName: 'sudoKey' }]
-  ),
+  withCalls<Props>(['query.sudo.key', { propName: 'sudoKey' }]),
   withObservable(accountObservable.subject, { propName: 'allAccounts' })
 );

+ 30 - 24
packages/apps/src/TopBar.tsx

@@ -1,5 +1,5 @@
 import React from 'react';
-import { Link } from 'react-router-dom';
+// import { Link } from 'react-router-dom';
 import { I18nProps } from '@polkadot/react-components/types';
 import { useMyAccount } from '@polkadot/joy-utils/MyAccountContext';
 import { InputAddress } from '@polkadot/react-components';
@@ -9,33 +9,39 @@ import './TopBar.css';
 
 type Props = I18nProps & {};
 
-function renderAddress (address: string) {
-  const balance = <span className='label'>Balance: </span>;
+function renderAddress(address: string) {
+  const balance = <span className="label">Balance: </span>;
 
-  return <div className='JoyTopBar'>
-    <InputAddress
-      defaultValue={address}
-      help='My current key that signs transactions'
-      label='My key'
-      labelExtra={<Available label={balance} params={address} />}
-      type='account'
-    />
-  </div>;
+  return (
+    <div className="JoyTopBar">
+      <InputAddress
+        defaultValue={address}
+        help="My current key that signs transactions"
+        label="My key"
+        labelExtra={<Available label={balance} params={address} />}
+        type="account"
+      />
+    </div>
+  );
 }
 
-function renderNoAddress () {
-  return <div className='JoyTopBar NoMyAddress'>
-    <i className='warning sign icon'></i>
-    <span style={{ marginRight: '1rem' }}>You need to create a key if you want to use all features.</span>
-    <Link className='ui small button orange' to='/accounts'>Create key</Link>
-  </div>;
-}
+// function renderNoAddress() {
+//   return (
+//     <div className="JoyTopBar NoMyAddress">
+//       <i className="warning sign icon"></i>
+//       <span style={{ marginRight: '1rem' }}>You need to create a key if you want to use all features.</span>
+//       <Link className="ui small button orange" to="/accounts">
+//         Create key
+//       </Link>
+//     </div>
+//   );
+// }
 
-function Component (_props: Props) {
-  const { state: { address } } = useMyAccount();
-  return address
-    ? renderAddress(address)
-    : renderNoAddress();
+function Component(_props: Props) {
+  const {
+    state: { address }
+  } = useMyAccount();
+  return address ? renderAddress(address) : null;
 }
 
 export default translate(Component);

+ 124 - 109
packages/joy-members/src/EditForm.tsx

@@ -11,48 +11,48 @@ import * as JoyForms from '@polkadot/joy-utils/forms';
 import { SubmittableResult } from '@polkadot/api';
 import { MemberId, UserInfo, Profile, PaidTermId, PaidMembershipTerms } from '@joystream/types/members';
 import { OptionText } from '@joystream/types/';
-import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/MyAccount';
+import { MyAccountProps, withMyAccount, withOnlyMembers } from '@polkadot/joy-utils/MyAccount';
 import { queryMembershipToProp } from './utils';
 import { withCalls } from '@polkadot/react-api/index';
 import { Button, Message } from 'semantic-ui-react';
 import { formatBalance } from '@polkadot/util';
 import { TxFailedCallback, TxCallback } from '@polkadot/react-components/Status/types';
-import isEqual from 'lodash/isEqual'
+import isEqual from 'lodash/isEqual';
 
 // TODO get next settings from Substrate:
 const HANDLE_REGEX = /^[a-z0-9_]+$/;
 
-const buildSchema = (p: ValidationProps) => Yup.object().shape({
-  handle: Yup.string()
-    .matches(HANDLE_REGEX, 'Handle can have only lowercase letters (a-z), numbers (0-9) and underscores (_).')
-    .min(p.minHandleLength, `Handle is too short. Minimum length is ${p.minHandleLength} chars.`)
-    .max(p.maxHandleLength, `Handle is too long. Maximum length is ${p.maxHandleLength} chars.`)
-    .required('Handle is required'),
-  avatar: Yup.string()
-    .url('Avatar must be a valid URL of an image.')
-    .max(p.maxAvatarUriLength, `Avatar URL is too long. Maximum length is ${p.maxAvatarUriLength} chars.`),
-  about: Yup.string()
-    .max(p.maxAboutTextLength, `Text is too long. Maximum length is ${p.maxAboutTextLength} chars.`)
-});
+const buildSchema = (p: ValidationProps) =>
+  Yup.object().shape({
+    handle: Yup.string()
+      .matches(HANDLE_REGEX, 'Handle can have only lowercase letters (a-z), numbers (0-9) and underscores (_).')
+      .min(p.minHandleLength, `Handle is too short. Minimum length is ${p.minHandleLength} chars.`)
+      .max(p.maxHandleLength, `Handle is too long. Maximum length is ${p.maxHandleLength} chars.`)
+      .required('Handle is required'),
+    avatar: Yup.string()
+      .url('Avatar must be a valid URL of an image.')
+      .max(p.maxAvatarUriLength, `Avatar URL is too long. Maximum length is ${p.maxAvatarUriLength} chars.`),
+    about: Yup.string().max(p.maxAboutTextLength, `Text is too long. Maximum length is ${p.maxAboutTextLength} chars.`)
+  });
 
 type ValidationProps = {
-  minHandleLength: number,
-  maxHandleLength: number,
-  maxAvatarUriLength: number,
-  maxAboutTextLength: number
+  minHandleLength: number;
+  maxHandleLength: number;
+  maxAvatarUriLength: number;
+  maxAboutTextLength: number;
 };
 
 type OuterProps = ValidationProps & {
-  profile?: Profile,
-  paidTerms: PaidMembershipTerms,
-  paidTermId: PaidTermId,
-  memberId? : MemberId,
+  profile?: Profile;
+  paidTerms: PaidMembershipTerms;
+  paidTermId: PaidTermId;
+  memberId?: MemberId;
 };
 
 type FormValues = {
-  handle: string,
-  avatar: string,
-  about: string
+  handle: string;
+  avatar: string;
+  about: string;
 };
 
 type FieldName = keyof FormValues;
@@ -76,7 +76,7 @@ const InnerForm = (props: FormProps) => {
     isSubmitting,
     setSubmitting,
     resetForm,
-    memberId,
+    memberId
   } = props;
 
   const onSubmit = (sendTx: () => void) => {
@@ -102,91 +102,101 @@ const InnerForm = (props: FormProps) => {
 
   // TODO extract to forms.tsx
   const fieldToTextOption = (field: FieldName): OptionText => {
-    return isFieldChanged(field)
-      ? OptionText.some(values[field])
-      : OptionText.none();
+    return isFieldChanged(field) ? OptionText.some(values[field]) : OptionText.none();
   };
 
   const buildTxParams = () => {
     if (!isValid) return [];
 
     const userInfo = new UserInfo({
-      handle:     fieldToTextOption('handle'),
+      handle: fieldToTextOption('handle'),
       avatar_uri: fieldToTextOption('avatar'),
-      about:      fieldToTextOption('about')
+      about: fieldToTextOption('about')
     });
 
     if (profile) {
       // update profile
-      return [ memberId, userInfo ];
+      return [memberId, userInfo];
     } else {
       // register as new member
-      return [ paidTermId, userInfo ];
+      return [paidTermId, userInfo];
     }
   };
 
   // TODO show warning that you don't have enough balance to buy a membership
 
   return (
-    <Section title='My Membership Profile'>
-    <Form className='ui form JoyForm'>
-      <LabelledText name='handle' label='Handle/nickname' placeholder={`You can use a-z, 0-9 and underscores.`} style={{ maxWidth: '30rem' }} {...props}/>
-      <LabelledText name='avatar' label='Avatar URL' placeholder='Paste here an URL of your avatar image.' {...props}/>
-      <LabelledField name='about' label='About' {...props}>
-        <Field component='textarea' id='about' name='about' disabled={isSubmitting} rows={3} placeholder='Write here anything you would like to share about yourself with Joystream community.' />
-      </LabelledField>
-      {!profile && paidTerms &&
-        <Message warning style={{ display: 'block', marginBottom: '0' }}>
-          <p>Membership costs <b>{formatBalance(paidTerms.fee)}</b> tokens.</p>
-          <p>
-            <span>By clicking the "Register" button you agree to our </span>
-            <Link to={`/pages/tos`}>Terms of Service</Link>
-            <span> and </span>
-            <Link to={`/pages/privacy`}>Privacy Policy</Link>
-            .
-          </p>
-        </Message>
-      }
-      <LabelledField invisibleLabel {...props}>
-        <TxButton
-          type='submit'
-          size='large'
-          label={profile
-            ? 'Update my profile'
-            : 'Register'
-          }
-          isDisabled={!dirty || isSubmitting}
-          params={buildTxParams()}
-          tx={profile
-            ? 'members.updateProfile'
-            : 'members.buyMembership'
-          }
-          onClick={onSubmit}
-          txFailedCb={onTxFailed}
-          txSuccessCb={onTxSuccess}
+    <Section title="My Membership Profile">
+      <Form className="ui form JoyForm">
+        <LabelledText
+          name="handle"
+          label="Handle/nickname"
+          placeholder={`You can use a-z, 0-9 and underscores.`}
+          style={{ maxWidth: '30rem' }}
+          {...props}
         />
-        <Button
-          type='button'
-          size='large'
-          disabled={!dirty || isSubmitting}
-          onClick={() => resetForm()}
-          content='Reset form'
+        <LabelledText
+          name="avatar"
+          label="Avatar URL"
+          placeholder="Paste here an URL of your avatar image."
+          {...props}
         />
-      </LabelledField>
-    </Form>
+        <LabelledField name="about" label="About" {...props}>
+          <Field
+            component="textarea"
+            id="about"
+            name="about"
+            disabled={isSubmitting}
+            rows={3}
+            placeholder="Write here anything you would like to share about yourself with Joystream community."
+          />
+        </LabelledField>
+        {!profile && paidTerms && (
+          <Message warning style={{ display: 'block', marginBottom: '0' }}>
+            <p>
+              Membership costs <b>{formatBalance(paidTerms.fee)}</b> tokens.
+            </p>
+            <p>
+              <span>By clicking the "Register" button you agree to our </span>
+              <Link to={`/pages/tos`}>Terms of Service</Link>
+              <span> and </span>
+              <Link to={`/pages/privacy`}>Privacy Policy</Link>.
+            </p>
+          </Message>
+        )}
+        <LabelledField invisibleLabel {...props}>
+          <TxButton
+            type="submit"
+            size="large"
+            label={profile ? 'Update my profile' : 'Register'}
+            isDisabled={!dirty || isSubmitting}
+            params={buildTxParams()}
+            tx={profile ? 'members.updateProfile' : 'members.buyMembership'}
+            onClick={onSubmit}
+            txFailedCb={onTxFailed}
+            txSuccessCb={onTxSuccess}
+          />
+          <Button
+            type="button"
+            size="large"
+            disabled={!dirty || isSubmitting}
+            onClick={() => resetForm()}
+            content="Reset form"
+          />
+        </LabelledField>
+      </Form>
     </Section>
   );
 };
 
 const EditForm = withFormik<OuterProps, FormValues>({
-
   // Transform outer props into form values
   mapPropsToValues: props => {
     const { profile: p } = props;
     return {
       handle: p ? p.handle.toString() : '',
       avatar: p ? p.avatar_uri.toString() : '',
-      about:  p ? p.about.toString() : ''
+      about: p ? p.about.toString() : ''
     };
   },
 
@@ -198,17 +208,17 @@ const EditForm = withFormik<OuterProps, FormValues>({
 })(InnerForm);
 
 type WithMyProfileProps = {
-  memberId?: MemberId,
-  memberProfile?: Option<any>, // TODO refactor to Option<Profile>
-  paidTermsId: PaidTermId,
-  paidTerms?: Option<PaidMembershipTerms>,
-  minHandleLength?: BN,
-  maxHandleLength?: BN,
-  maxAvatarUriLength?: BN,
-  maxAboutTextLength?: BN
+  memberId?: MemberId;
+  memberProfile?: Option<any>; // TODO refactor to Option<Profile>
+  paidTermsId: PaidTermId;
+  paidTerms?: Option<PaidMembershipTerms>;
+  minHandleLength?: BN;
+  maxHandleLength?: BN;
+  maxAvatarUriLength?: BN;
+  maxAboutTextLength?: BN;
 };
 
-function WithMyProfileInner (p: WithMyProfileProps) {
+function WithMyProfileInner(p: WithMyProfileProps) {
   const triedToFindProfile = !p.memberId || p.memberProfile;
   if (
     triedToFindProfile &&
@@ -224,16 +234,18 @@ function WithMyProfileInner (p: WithMyProfileProps) {
       console.error('Could not find active paid membership terms');
     }
 
-    return <EditForm
-      minHandleLength={p.minHandleLength.toNumber()}
-      maxHandleLength={p.maxHandleLength.toNumber()}
-      maxAvatarUriLength={p.maxAvatarUriLength.toNumber()}
-      maxAboutTextLength={p.maxAboutTextLength.toNumber()}
-      profile={profile as Profile}
-      paidTerms={p.paidTerms.unwrap()}
-      paidTermId={p.paidTermsId}
-      memberId={p.memberId}
-    />;
+    return (
+      <EditForm
+        minHandleLength={p.minHandleLength.toNumber()}
+        maxHandleLength={p.maxHandleLength.toNumber()}
+        maxAvatarUriLength={p.maxAvatarUriLength.toNumber()}
+        maxAboutTextLength={p.maxAboutTextLength.toNumber()}
+        profile={profile as Profile}
+        paidTerms={p.paidTerms.unwrap()}
+        paidTermId={p.paidTermsId}
+        memberId={p.memberId}
+      />
+    );
   } else return <em>Loading...</em>;
 }
 
@@ -243,17 +255,16 @@ const WithMyProfile = withCalls<WithMyProfileProps>(
   queryMembershipToProp('maxAvatarUriLength'),
   queryMembershipToProp('maxAboutTextLength'),
   queryMembershipToProp('memberProfile', 'memberId'),
-  queryMembershipToProp('paidMembershipTermsById',
-    { paramName: 'paidTermsId', propName: 'paidTerms' })
+  queryMembershipToProp('paidMembershipTermsById', { paramName: 'paidTermsId', propName: 'paidTerms' })
 )(WithMyProfileInner);
 
 type WithMyMemberIdProps = MyAccountProps & {
-  memberIdsByRootAccountId?: Vec<MemberId>,
-  memberIdsByControllerAccountId?: Vec<MemberId>,
-  paidTermsIds?: Vec<PaidTermId>
+  memberIdsByRootAccountId?: Vec<MemberId>;
+  memberIdsByControllerAccountId?: Vec<MemberId>;
+  paidTermsIds?: Vec<PaidTermId>;
 };
 
-function WithMyMemberIdInner (p: WithMyMemberIdProps) {
+function WithMyMemberIdInner(p: WithMyMemberIdProps) {
   if (p.memberIdsByRootAccountId && p.memberIdsByControllerAccountId && p.paidTermsIds) {
     if (p.paidTermsIds.length) {
       // let member_ids = p.memberIdsByRootAccountId.slice(); // u8a.subarray is not a function!!
@@ -268,10 +279,14 @@ function WithMyMemberIdInner (p: WithMyMemberIdProps) {
   return <em>Loading...</em>;
 }
 
-const WithMyMemberId = withMyAccount(withCalls<WithMyMemberIdProps>(
-  queryMembershipToProp('memberIdsByRootAccountId', 'myAddress'),
-  queryMembershipToProp('memberIdsByControllerAccountId', 'myAddress'),
-  queryMembershipToProp('activePaidMembershipTerms', { propName: 'paidTermsIds' })
-)(WithMyMemberIdInner));
+const WithMyMemberId = withOnlyMembers(
+  withMyAccount(
+    withCalls<WithMyMemberIdProps>(
+      queryMembershipToProp('memberIdsByRootAccountId', 'myAddress'),
+      queryMembershipToProp('memberIdsByControllerAccountId', 'myAddress'),
+      queryMembershipToProp('activePaidMembershipTerms', { propName: 'paidTermsIds' })
+    )(WithMyMemberIdInner)
+  )
+);
 
 export default WithMyMemberId;

+ 21 - 41
packages/joy-storage/src/index.tsx

@@ -19,18 +19,20 @@ import './index.css';
 
 import translate from './translate';
 
-type Props = AppProps & ApiProps & I18nProps & {
-  requests?: Array<Request>,
-  actorAccountIds?: Array<AccountId>,
-  roles?: Array<Role>,
-  allAccounts?: SubjectInfo,
-};
+type Props = AppProps &
+  ApiProps &
+  I18nProps & {
+    requests?: Array<Request>;
+    actorAccountIds?: Array<AccountId>;
+    roles?: Array<Role>;
+    allAccounts?: SubjectInfo;
+  };
 
 type State = {
-  tabs: Array<TabItem>,
-  actorAccountIds: Array<string>,
-  requests: Array<Request>,
-  roles: Array<Role>,
+  tabs: Array<TabItem>;
+  actorAccountIds: Array<string>;
+  requests: Array<Request>;
+  roles: Array<Role>;
 };
 
 class App extends React.PureComponent<Props, State> {
@@ -58,22 +60,16 @@ class App extends React.PureComponent<Props, State> {
         {
           name: 'requests',
           text: t('My Staking Requests')
-        },
-      ],
+        }
+      ]
     };
   }
 
   static getDerivedStateFromProps({ actorAccountIds, requests, roles }: Props): State {
     return {
-      actorAccountIds: (actorAccountIds || []).map((accountId) =>
-        accountId.toString()
-      ),
-      requests: (requests || []).map((request) =>
-        request
-      ),
-      roles: (roles || []).map((role) =>
-        role
-      ),
+      actorAccountIds: (actorAccountIds || []).map(accountId => accountId.toString()),
+      requests: (requests || []).map(request => request),
+      roles: (roles || []).map(role => role)
     } as State;
   }
 
@@ -81,20 +77,11 @@ class App extends React.PureComponent<Props, State> {
     const { allAccounts } = this.props;
     const { tabs } = this.state;
     const { basePath } = this.props;
-    const hasAccounts = allAccounts && Object.keys(allAccounts).length;
-    const filteredTabs = hasAccounts
-      ? tabs
-      : tabs.filter(({ name }) =>
-        !['requests'].includes(name)
-      );
 
     return (
-      <main className='actors--App'>
+      <main className="actors--App">
         <header>
-          <Tabs
-            basePath={basePath}
-            items={filteredTabs}
-          />
+          <Tabs basePath={basePath} items={tabs} />
         </header>
         <Switch>
           <Route path={`${basePath}/requests`} render={this.renderComponent(MyRequests)} />
@@ -109,16 +96,9 @@ class App extends React.PureComponent<Props, State> {
     return (): React.ReactNode => {
       const { actorAccountIds, requests, roles } = this.state;
 
-      return (
-        <Component
-          actorAccountIds={actorAccountIds}
-          requests={requests}
-          roles={roles}
-        />
-      );
+      return <Component actorAccountIds={actorAccountIds} requests={requests} roles={roles} />;
     };
   }
-
 }
 
 export default withMulti(
@@ -128,6 +108,6 @@ export default withMulti(
   withCalls<Props>(
     ['query.actors.actorAccountIds', { propName: 'actorAccountIds' }],
     ['query.actors.roleEntryRequests', { propName: 'requests' }],
-    ['query.actors.availableRoles', { propName: 'roles' }],
+    ['query.actors.availableRoles', { propName: 'roles' }]
   )
 );

+ 125 - 129
packages/joy-utils/src/MyAccount.tsx

@@ -16,38 +16,40 @@ import { useMyAccount } from '@polkadot/joy-utils/MyAccountContext';
 import { queryToProp, MultipleLinkedMapEntry, SingleLinkedMapEntry } from '@polkadot/joy-utils/index';
 
 export type MyAddressProps = {
-  myAddress?: string
+  myAddress?: string;
 };
 
 export type MyAccountProps = MyAddressProps & {
-  myAccountId?: AccountId,
-  myMemberId?: MemberId,
-  memberIdsByRootAccountId?: Vec<MemberId>,
-  memberIdsByControllerAccountId?: Vec<MemberId>,
-  myMemberIdChecked?: boolean,
-  iAmMember?: boolean,
-  memberProfile?: Option<any>,
+  myAccountId?: AccountId;
+  myMemberId?: MemberId;
+  memberIdsByRootAccountId?: Vec<MemberId>;
+  memberIdsByControllerAccountId?: Vec<MemberId>;
+  myMemberIdChecked?: boolean;
+  iAmMember?: boolean;
+  memberProfile?: Option<any>;
 
   // Content Working Group
-  curatorEntries?: any, //entire linked_map: CuratorId => Curator
-  isLeadSet?: Option<LeadId>
-  contentLeadId? : LeadId
-  contentLeadEntry?: any // linked_map value
+  curatorEntries?: any; //entire linked_map: CuratorId => Curator
+  isLeadSet?: Option<LeadId>;
+  contentLeadId?: LeadId;
+  contentLeadEntry?: any; // linked_map value
 
   // From member's roles
-  myContentLeadId?: LeadId,
-  myCuratorIds?: CuratorId[],
-  memberIsCurator?: boolean,
-  memberIsContentLead?: boolean,
+  myContentLeadId?: LeadId;
+  myCuratorIds?: CuratorId[];
+  memberIsCurator?: boolean;
+  memberIsContentLead?: boolean;
 
-  curationActor?: any,
-  allAccounts?: SubjectInfo,
+  curationActor?: any;
+  allAccounts?: SubjectInfo;
 };
 
-function withMyAddress<P extends MyAccountProps> (Component: React.ComponentType<P>) {
-  return function (props: P) {
-    const { state: { address } } = useMyAccount();
-    const myAccountId = address ? new GenericAccountId(address) : undefined
+function withMyAddress<P extends MyAccountProps>(Component: React.ComponentType<P>) {
+  return function(props: P) {
+    const {
+      state: { address }
+    } = useMyAccount();
+    const myAccountId = address ? new GenericAccountId(address) : undefined;
     return <Component myAddress={address} myAccountId={myAccountId} {...props} />;
   };
 }
@@ -57,8 +59,8 @@ const withMyMemberIds = withCalls<MyAccountProps>(
   queryMembershipToProp('memberIdsByControllerAccountId', 'myAddress')
 );
 
-function withMyMembership<P extends MyAccountProps> (Component: React.ComponentType<P>) {
-  return function (props: P) {
+function withMyMembership<P extends MyAccountProps>(Component: React.ComponentType<P>) {
+  return function(props: P) {
     const { memberIdsByRootAccountId, memberIdsByControllerAccountId } = props;
 
     const myMemberIdChecked = memberIdsByRootAccountId && memberIdsByControllerAccountId;
@@ -76,55 +78,47 @@ function withMyMembership<P extends MyAccountProps> (Component: React.ComponentT
     const newProps = {
       myMemberIdChecked,
       myMemberId,
-      iAmMember,
+      iAmMember
     };
 
     return <Component {...props} {...newProps} />;
   };
 }
 
-const withMyProfile = withCalls<MyAccountProps>(
-  queryMembershipToProp('memberProfile', 'myMemberId'),
-);
+const withMyProfile = withCalls<MyAccountProps>(queryMembershipToProp('memberProfile', 'myMemberId'));
 
 const withContentWorkingGroupDetails = withCalls<MyAccountProps>(
-  queryToProp('query.contentWorkingGroup.currentLeadId', { propName: 'isLeadSet'}),
-  queryToProp('query.contentWorkingGroup.curatorById', { propName: 'curatorEntries' }),
+  queryToProp('query.contentWorkingGroup.currentLeadId', { propName: 'isLeadSet' }),
+  queryToProp('query.contentWorkingGroup.curatorById', { propName: 'curatorEntries' })
 );
 
-function resolveLead<P extends MyAccountProps> (Component: React.ComponentType<P>) {
-  return function (props: P) {
+function resolveLead<P extends MyAccountProps>(Component: React.ComponentType<P>) {
+  return function(props: P) {
     const { isLeadSet } = props;
 
     let contentLeadId;
 
     if (isLeadSet && isLeadSet.isSome) {
-      contentLeadId = isLeadSet.unwrap()
+      contentLeadId = isLeadSet.unwrap();
     }
 
     let newProps = {
       contentLeadId
-    }
+    };
 
     return <Component {...props} {...newProps} />;
-  }
+  };
 }
 
 const resolveLeadEntry = withCalls<MyAccountProps>(
-  queryToProp('query.contentWorkingGroup.leadById', { propName: 'contentLeadEntry',  paramName: 'contentLeadId' }),
-);
-
-const withContentWorkingGroup = <P extends MyAccountProps> (Component: React.ComponentType<P>) =>
-withMulti(
-  Component,
-  withContentWorkingGroupDetails,
-  resolveLead,
-  resolveLeadEntry,
+  queryToProp('query.contentWorkingGroup.leadById', { propName: 'contentLeadEntry', paramName: 'contentLeadId' })
 );
 
-function withMyRoles<P extends MyAccountProps> (Component: React.ComponentType<P>) {
-  return function (props: P) {
+const withContentWorkingGroup = <P extends MyAccountProps>(Component: React.ComponentType<P>) =>
+  withMulti(Component, withContentWorkingGroupDetails, resolveLead, resolveLeadEntry);
 
+function withMyRoles<P extends MyAccountProps>(Component: React.ComponentType<P>) {
+  return function(props: P) {
     const { iAmMember, memberProfile } = props;
 
     let myContentLeadId;
@@ -132,7 +126,7 @@ function withMyRoles<P extends MyAccountProps> (Component: React.ComponentType<P
 
     if (iAmMember && memberProfile && memberProfile.isSome) {
       const profile = memberProfile.unwrap() as Profile;
-      profile.roles.forEach((role) => {
+      profile.roles.forEach(role => {
         if (role.isContentLead) {
           myContentLeadId = role.actor_id;
         } else if (role.isCurator) {
@@ -148,96 +142,90 @@ function withMyRoles<P extends MyAccountProps> (Component: React.ComponentType<P
       memberIsContentLead,
       memberIsCurator,
       myContentLeadId,
-      myCuratorIds,
+      myCuratorIds
     };
 
     return <Component {...props} {...newProps} />;
-  }
+  };
 }
 
 const canUseAccount = (account: AccountId, allAccounts: SubjectInfo | undefined) => {
   if (!allAccounts || !Object.keys(allAccounts).length) {
-    return false
+    return false;
   }
 
-  const ix = Object.keys(allAccounts).findIndex((key) => {
-    return account.eq(allAccounts[key].json.address)
+  const ix = Object.keys(allAccounts).findIndex(key => {
+    return account.eq(allAccounts[key].json.address);
   });
 
-  return ix != -1
-}
-
-function withCurationActor<P extends MyAccountProps> (Component: React.ComponentType<P>) {
-  return function (props: P) {
+  return ix != -1;
+};
 
+function withCurationActor<P extends MyAccountProps>(Component: React.ComponentType<P>) {
+  return function(props: P) {
     const {
-      myAccountId, isLeadSet, contentLeadEntry,
-      myCuratorIds, curatorEntries, allAccounts,
-      memberIsContentLead, memberIsCurator
+      myAccountId,
+      isLeadSet,
+      contentLeadEntry,
+      myCuratorIds,
+      curatorEntries,
+      allAccounts,
+      memberIsContentLead,
+      memberIsCurator
     } = props;
 
     if (!myAccountId || !isLeadSet || !contentLeadEntry || !curatorEntries || !allAccounts) {
       return <Component {...props} />;
     }
 
-    const leadRoleAccount = isLeadSet.isSome ?
-      new SingleLinkedMapEntry<Lead>(Lead, contentLeadEntry).value.role_account : null;
+    const leadRoleAccount = isLeadSet.isSome
+      ? new SingleLinkedMapEntry<Lead>(Lead, contentLeadEntry).value.role_account
+      : null;
 
     // Is current key the content lead key?
     if (leadRoleAccount && leadRoleAccount.eq(myAccountId)) {
-      return <Component {...props} curationActor={[
-        new CurationActor('Lead'),
-        myAccountId
-      ]} />
+      return <Component {...props} curationActor={[new CurationActor('Lead'), myAccountId]} />;
     }
 
-    const curators = new MultipleLinkedMapEntry<CuratorId, Curator>(
-      CuratorId,
-      Curator,
-      curatorEntries
-    );
+    const curators = new MultipleLinkedMapEntry<CuratorId, Curator>(CuratorId, Curator, curatorEntries);
 
     const correspondingCurationActor = (accountId: AccountId, curators: MultipleLinkedMapEntry<CuratorId, Curator>) => {
-      const ix = curators.linked_values.findIndex(
-        curator => myAccountId.eq(curator.role_account) && curator.is_active
-      );
+      const ix = curators.linked_values.findIndex(curator => myAccountId.eq(curator.role_account) && curator.is_active);
 
-      return ix >= 0 ? new CurationActor({
-          'Curator':  curators.linked_keys[ix]
-        }) : null;
-    }
+      return ix >= 0
+        ? new CurationActor({
+            Curator: curators.linked_keys[ix]
+          })
+        : null;
+    };
 
     const firstMatchingCurationActor = correspondingCurationActor(myAccountId, curators);
 
     // Is the current key corresponding to an active curator role key?
     if (firstMatchingCurationActor) {
-      return <Component {...props} curationActor={[
-        firstMatchingCurationActor,
-        myAccountId
-      ]} />;
+      return <Component {...props} curationActor={[firstMatchingCurationActor, myAccountId]} />;
     }
 
     // See if we have the member's lead role account
-    if(leadRoleAccount && memberIsContentLead && canUseAccount(leadRoleAccount, allAccounts)) {
-      return <Component {...props} curationActor={[
-        new CurationActor('Lead'),
-        leadRoleAccount
-      ]} />
+    if (leadRoleAccount && memberIsContentLead && canUseAccount(leadRoleAccount, allAccounts)) {
+      return <Component {...props} curationActor={[new CurationActor('Lead'), leadRoleAccount]} />;
     }
 
     // See if we have one of the member's curator role accounts
-    if(memberIsCurator && myCuratorIds && curators.linked_keys.length) {
-      for(let i = 0; i < myCuratorIds.length; i++) {
+    if (memberIsCurator && myCuratorIds && curators.linked_keys.length) {
+      for (let i = 0; i < myCuratorIds.length; i++) {
         const curator_id = myCuratorIds[i];
-        const ix = curators.linked_keys.findIndex((id) => id.eq(curator_id));
+        const ix = curators.linked_keys.findIndex(id => id.eq(curator_id));
 
         if (ix >= 0) {
           const curator = curators.linked_values[ix];
           if (curator.is_active && canUseAccount(curator.role_account, allAccounts)) {
-            return <Component {...props} curationActor={[
-              new CurationActor({ 'Curator': curator_id }),
-              curator.role_account
-            ]} />;
+            return (
+              <Component
+                {...props}
+                curationActor={[new CurationActor({ Curator: curator_id }), curator.role_account]}
+              />
+            );
           }
         }
       }
@@ -247,58 +235,70 @@ function withCurationActor<P extends MyAccountProps> (Component: React.Component
 
     // Use lead role key if available
     if (leadRoleAccount && canUseAccount(leadRoleAccount, allAccounts)) {
-      return <Component {...props} curationActor={[
-        new CurationActor('Lead'),
-        leadRoleAccount
-      ]} />
+      return <Component {...props} curationActor={[new CurationActor('Lead'), leadRoleAccount]} />;
     }
 
     // Use first available active curator role key if available
-    if(curators.linked_keys.length) {
+    if (curators.linked_keys.length) {
       for (let i = 0; i < curators.linked_keys.length; i++) {
         let curator = curators.linked_values[i];
         if (curator.is_active && canUseAccount(curator.role_account, allAccounts)) {
-          return <Component {...props} curationActor={[
-            new CurationActor({ 'Curator':  curators.linked_keys[i] }),
-            curator.role_account
-          ]} />
+          return (
+            <Component
+              {...props}
+              curationActor={[new CurationActor({ Curator: curators.linked_keys[i] }), curator.role_account]}
+            />
+          );
         }
       }
     }
 
     // we don't have any key that can fulfill a curation action
     return <Component {...props} />;
-  }
+  };
 }
 
-export const withMyAccount = <P extends MyAccountProps> (Component: React.ComponentType<P>) =>
-withMulti(
-  Component,
-  withObservable(accountObservable.subject, { propName: 'allAccounts' }),
-  withMyAddress,
-  withMyMemberIds,
-  withMyMembership,
-  withMyProfile,
-  withContentWorkingGroup,
-  withMyRoles,
-  withCurationActor,
-);
-
-function OnlyMembers<P extends MyAccountProps> (Component: React.ComponentType<P>) {
-  return function (props: P) {
+export const withMyAccount = <P extends MyAccountProps>(Component: React.ComponentType<P>) =>
+  withMulti(
+    Component,
+    withObservable(accountObservable.subject, { propName: 'allAccounts' }),
+    withMyAddress,
+    withMyMemberIds,
+    withMyMembership,
+    withMyProfile,
+    withContentWorkingGroup,
+    withMyRoles,
+    withCurationActor
+  );
+
+function OnlyMembers<P extends MyAccountProps>(Component: React.ComponentType<P>) {
+  return function(props: P) {
     const { myMemberIdChecked, iAmMember } = props;
     if (!myMemberIdChecked) {
-      return <em>Loading...</em>;
+      return (
+        <Message warning className="JoyMainStatus">
+          <Message.Header>Please create a key to get started.</Message.Header>
+          <div style={{ marginTop: '1rem' }}>
+            <Link to={`/accounts`} className="ui button orange">
+              Create key
+            </Link>
+          </div>
+        </Message>
+      );
     } else if (iAmMember) {
       return <Component {...props} />;
     } else {
       return (
-        <Message warning className='JoyMainStatus'>
+        <Message warning className="JoyMainStatus">
           <Message.Header>Only members can access this functionality.</Message.Header>
           <div style={{ marginTop: '1rem' }}>
-            <Link to={`/members/edit`} className='ui button orange'>Register here</Link>
+            <Link to={`/members/edit`} className="ui button orange">
+              Register here
+            </Link>
             <span style={{ margin: '0 .5rem' }}> or </span>
-            <Link to={`/accounts`} className='ui button'>Change key</Link>
+            <Link to={`/accounts`} className="ui button">
+              Change key
+            </Link>
           </div>
         </Message>
       );
@@ -306,9 +306,5 @@ function OnlyMembers<P extends MyAccountProps> (Component: React.ComponentType<P
   };
 }
 
-export const withOnlyMembers = <P extends MyAccountProps> (Component: React.ComponentType<P>) =>
-withMulti(
-  Component,
-  withMyAccount,
-  OnlyMembers
-);
+export const withOnlyMembers = <P extends MyAccountProps>(Component: React.ComponentType<P>) =>
+  withMulti(Component, withMyAccount, OnlyMembers);

+ 2 - 3
packages/react-components/src/FilterOverlay.tsx

@@ -13,9 +13,9 @@ interface Props extends BareProps {
   children: React.ReactNode;
 }
 
-function FilterOverlay ({ children, className }: Props): React.ReactElement<Props> {
+function FilterOverlay({ children, className, style }: Props): React.ReactElement<Props> {
   return (
-    <div className={className}>
+    <div className={className} style={style}>
       {children}
     </div>
   );
@@ -33,7 +33,6 @@ export default styled(FilterOverlay)`
     justify-content: flex-end;
     position: absolute;
     right: 5rem;
-    top: 5.5rem;
 
     > div {
       max-width: 35rem !important;

+ 7 - 18
packages/react-components/src/HelpOverlay.tsx

@@ -14,31 +14,21 @@ interface Props extends BareProps {
   md: string;
 }
 
-function HelpOverlay ({ className, md }: Props): React.ReactElement<Props> {
+function HelpOverlay({ className, md, style }: Props): React.ReactElement<Props> {
   const [isVisible, setIsVisible] = useState(false);
 
   const _toggleVisible = (): void => setIsVisible(!isVisible);
 
   return (
     <div className={className}>
-      <div className='help-button'>
-        <Icon
-          name='help circle'
-          onClick={_toggleVisible}
-        />
+      <div className="help-button" style={style}>
+        <Icon name="help circle" onClick={_toggleVisible} />
       </div>
       <div className={`help-slideout ${isVisible ? 'open' : 'closed'}`}>
-        <div className='help-button'>
-          <Icon
-            name='close'
-            onClick={_toggleVisible}
-          />
+        <div className="help-button">
+          <Icon name="close" onClick={_toggleVisible} />
         </div>
-        <ReactMd
-          className='help-content'
-          escapeHtml={false}
-          source={md}
-        />
+        <ReactMd className="help-content" escapeHtml={false} source={md} />
       </div>
     </div>
   );
@@ -55,7 +45,6 @@ export default styled(HelpOverlay)`
   > .help-button {
     position: absolute;
     right: 0rem;
-    top: 5.25rem;
   }
 
   .help-slideout {
@@ -67,7 +56,7 @@ export default styled(HelpOverlay)`
     position: fixed;
     right: -50rem;
     top: 0;
-    transition-duration: .5s;
+    transition-duration: 0.5s;
     transition-property: all;
     z-index: 10;