Browse Source

Merge branch 'joystream-constantinople' into joy-types-bump-version

Mokhtar Naamani 4 years ago
parent
commit
f99742a446

+ 16 - 7
packages/joy-members/src/Details.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 import { Link } from 'react-router-dom';
-import { Table } from 'semantic-ui-react';
+import { Table, Loader } from 'semantic-ui-react';
 import ReactMarkdown from 'react-markdown';
 import { IdentityIcon } from '@polkadot/react-components';
 import { ApiProps } from '@polkadot/react-api/types';
@@ -21,17 +21,23 @@ import { MyAccountProps, withMyAccount } from '@polkadot/joy-utils/MyAccount';
 type Props = ApiProps & I18nProps & MyAccountProps & {
   preview?: boolean,
   memberId: MemberId,
-  memberProfile?: Option<any>, // TODO refactor to Option<Profile>
+  // This cannot be named just "memberProfile", since it will conflict with "withAccount's" memberProfile
+  // (which holds  member profile associated with currently selected account)
+  detailsMemberProfile?: Option<any>, // TODO refactor to Option<Profile>
   activeCouncil?: Seat[]
 };
 
 class Component extends React.PureComponent<Props> {
 
   render () {
-    const { memberProfile } = this.props;
-    return memberProfile
-      ? this.renderProfile(memberProfile.unwrap() as Profile)
-      : null;
+    const { detailsMemberProfile } = this.props;
+    return detailsMemberProfile
+      ? this.renderProfile(detailsMemberProfile.unwrap() as Profile)
+      : (
+        <div className={`item ProfileDetails`}>
+          <Loader active inline/>
+        </div>
+      );
   }
 
   private renderProfile (memberProfile: Profile) {
@@ -167,6 +173,9 @@ class Component extends React.PureComponent<Props> {
 export default translate(withMyAccount(
   withCalls<Props>(
     queryToProp('query.council.activeCouncil'),
-    queryMembershipToProp('memberProfile', 'memberId'),
+    queryMembershipToProp(
+      'memberProfile',
+      { paramName: 'memberId', propName: 'detailsMemberProfile' }
+    ),
   )(Component)
 ));

+ 61 - 14
packages/joy-members/src/List.tsx

@@ -8,43 +8,90 @@ import Section from '@polkadot/joy-utils/Section';
 import translate from './translate';
 import Details from './Details';
 import { MemberId } from '@joystream/types/members';
+import { RouteComponentProps, Redirect } from 'react-router-dom';
+import { Pagination, Icon, PaginationProps } from 'semantic-ui-react';
+import styled from 'styled-components';
 
-type Props = ApiProps & I18nProps & {
+const StyledPagination = styled(Pagination)`
+  border-bottom: 1px solid #ddd !important;
+`;
+
+type Props = ApiProps & I18nProps & RouteComponentProps & {
   firstMemberId: BN,
-  membersCreated: BN
+  membersCreated: BN,
+  match: { params: { page?: string } }
 };
 
 type State = {};
 
+const MEMBERS_PER_PAGE = 20;
+
 class Component extends React.PureComponent<Props, State> {
 
   state: State = {};
 
+  onPageChange = (e: React.MouseEvent, data: PaginationProps) => {
+    const { history } = this.props;
+    history.push(`/members/list/${ data.activePage }`);
+  }
+
+  renderPagination(currentPage:number, pagesCount: number) {
+    return (
+      <StyledPagination
+        pointing
+        secondary
+        activePage={ currentPage }
+        ellipsisItem={{ content: <Icon name='ellipsis horizontal' />, icon: true }}
+        firstItem={{ content: <Icon name='angle double left' />, icon: true }}
+        lastItem={{ content: <Icon name='angle double right' />, icon: true }}
+        prevItem={{ content: <Icon name='angle left' />, icon: true }}
+        nextItem={{ content: <Icon name='angle right' />, icon: true }}
+        totalPages={ pagesCount }
+        onPageChange={ this.onPageChange }
+      />
+    )
+  }
+
   render () {
     const {
       firstMemberId,
-      membersCreated
+      membersCreated,
+      match: { params: { page } }
     } = this.props;
 
     const membersCount = membersCreated.toNumber();
+    const pagesCount = Math.ceil(membersCount / MEMBERS_PER_PAGE) || 1;
+    const currentPage = Math.min(parseInt(page || '1'), pagesCount);
+
+    if (currentPage.toString() !== page) {
+      return <Redirect to={ `/members/list/${ currentPage }` } />;
+    }
+
     const ids: MemberId[] = [];
     if (membersCount > 0) {
-      const firstId = firstMemberId.toNumber();
-      for (let i = firstId; i < membersCount; i++) {
+      const firstId = firstMemberId.toNumber() + (currentPage - 1) * MEMBERS_PER_PAGE;
+      const lastId = Math.min(firstId + MEMBERS_PER_PAGE, membersCount) - 1;
+      for (let i = firstId; i <= lastId; i++) {
         ids.push(new MemberId(i));
       }
     }
 
     return (
-      <Section title={`Members (${membersCount})`}>{
-        ids.length === 0
-          ? <em>No registered members yet.</em>
-          : <div className='ui huge relaxed middle aligned divided list ProfilePreviews'>
-              {ids.map((id, i) =>
-                <Details {...this.props} key={i} memberId={id} preview />
-              )}
-            </div>
-      }</Section>
+      <Section
+        title={`Members (${membersCount})`}
+        pagination={ (pagesCount > 1 && this.renderPagination(currentPage, pagesCount)) || undefined }>
+        {
+          membersCount === 0
+            ? <em>No registered members yet.</em>
+            : (
+              <div className='ui huge relaxed middle aligned divided list ProfilePreviews'>
+                {ids.map((id, i) =>
+                  <Details {...this.props} key={i} memberId={id} preview />
+                )}
+              </div>
+            )
+        }
+      </Section>
     );
   }
 }

+ 9 - 8
packages/joy-members/src/index.tsx

@@ -18,6 +18,7 @@ import DetailsByHandle from './DetailsByHandle';
 import EditForm from './EditForm';
 import { withMyAccount, MyAccountProps } from '@polkadot/joy-utils/MyAccount';
 import {FIRST_MEMBER_ID} from './constants';
+import { RouteComponentProps } from 'react-router-dom';
 
 // define out internal types
 type Props = AppProps & ApiProps & I18nProps & MyAccountProps & {
@@ -31,9 +32,9 @@ class App extends React.PureComponent<Props> {
 
     return [
       {
-        isRoot: true,
-        name: 'members',
-        text: t('All members') + ` (${memberCount})`
+        name: 'list',
+        text: t('All members') + ` (${memberCount})`,
+        forcedExact: false
       },
       {
         name: 'edit',
@@ -46,17 +47,16 @@ class App extends React.PureComponent<Props> {
     ];
   }
 
-  private renderList () {
+  private renderList (routeProps: RouteComponentProps) {
     const { membersCreated, ...otherProps } = this.props;
     return membersCreated ?
-      <List firstMemberId={FIRST_MEMBER_ID} membersCreated={membersCreated} {...otherProps} />
+      <List firstMemberId={FIRST_MEMBER_ID} membersCreated={membersCreated} {...otherProps} {...routeProps}/>
       : <em>Loading...</em>;
   }
 
   render () {
     const { basePath } = this.props;
     const tabs = this.buildTabs();
-    const list = () => this.renderList();
 
     return (
       <main className='members--App'>
@@ -66,8 +66,9 @@ class App extends React.PureComponent<Props> {
         <Switch>
           <Route path={`${basePath}/edit`} component={EditForm} />
           <Route path={`${basePath}/dashboard`} component={Dashboard} />
-          <Route path={`${basePath}/:handle`} component={DetailsByHandle} />
-          <Route render={list} />
+          <Route path={`${basePath}/list/:page([0-9]+)?`} render={ props => this.renderList(props) } />
+          <Route exact={true} path={`${basePath}/:handle`} component={DetailsByHandle} />
+          <Route render={ props => this.renderList(props) } />
         </Switch>
       </main>
     );

+ 46 - 5
packages/joy-utils/src/Section.tsx

@@ -1,34 +1,75 @@
 import React from 'react';
 import { BareProps } from '@polkadot/react-components/types';
+import styled from 'styled-components';
+
+const Header = styled.div`
+  display: flex;
+  width: 100%;
+  justify-content: space-between;
+  align-items: flex-end;
+  margin-bottom: ${ (props: { withPagination: boolean }) => props.withPagination ? '1rem' : 0 };
+  flex-wrap: wrap;
+`;
+
+const Title = styled.div`
+  flex-grow: 1;
+`;
+
+const ResponsivePagination = styled.div`
+  @media screen and (max-width: 767px) {
+    & a[type=firstItem],
+    & a[type=lastItem] {
+      display: none !important;
+    }
+    & a {
+      font-size: 0.8rem !important;
+    }
+  }
+`;
+
+const TopPagination = styled(ResponsivePagination)`
+  margin-left: auto;
+`;
+
+const BottomPagination = styled(ResponsivePagination)`
+  display: flex;
+  justify-content: flex-end;
+`;
 
 type Props = BareProps & {
   className?: string,
   title?: JSX.Element | string,
-  level?: number
+  level?: number,
+  pagination?: JSX.Element
 };
 
 export default class Section extends React.PureComponent<Props> {
 
   render () {
-    let { className, children } = this.props;
+    let { className, children, pagination } = this.props;
     className = (className || '') + ' JoySection';
 
     return (
       <section className={className}>
-        {this.renderTitle()}
+        <Header withPagination={ Boolean(pagination) }>
+          <Title>{this.renderTitle()}</Title>
+          { pagination && <TopPagination>{ pagination }</TopPagination> }
+        </Header>
         <div>{children}</div>
+        { pagination && <BottomPagination>{ pagination }</BottomPagination> }
       </section>
     );
   }
 
   private renderTitle = () => {
-    const { title, level = 2 } = this.props;
+    const { title, level = 2, pagination } = this.props;
     if (!title) return null;
 
     const className = 'JoySection-title';
+    const style = pagination ? { margin: '0' } : {};
     return React.createElement(
       `h${level}`,
-      { className },
+      { className, style },
       title
     );
   }

+ 4 - 1
packages/react-components/src/Tabs.tsx

@@ -22,6 +22,7 @@ export interface TabItem {
   hasParams?: boolean;
   isExact?: boolean;
   isRoot?: boolean;
+  forcedExact?: boolean;
   name: string;
   text: React.ReactNode;
 }
@@ -40,7 +41,9 @@ function renderItem ({ basePath, isSequence, items }: Props): (tabItem: TabItem,
       : `${basePath}/${name}`;
     // only do exact matching when not the fallback (first position tab),
     // params are problematic for dynamic hidden such as app-accounts
-    const isExact = tab.isExact || !hasParams || (!isSequence && index === 0);
+    const isExact = tab.forcedExact !== undefined ? tab.forcedExact : (
+      tab.isExact || !hasParams || (!isSequence && index === 0)
+    );
 
     return (
       <React.Fragment key={to}>