Browse Source

Fixes for propTypes, Minro UI improvements

develop
Apostolos Fanakis 6 years ago
parent
commit
9d481fc162
  1. 28
      app/src/CustomPropTypes.js
  2. 164
      app/src/containers/PlaceholderContainer.js
  3. 88
      app/src/containers/Post.js
  4. 44
      app/src/containers/PostList.js
  5. 6
      app/src/containers/ProfileContainer.js
  6. 106
      app/src/containers/ProfileInformation.js
  7. 5
      app/src/containers/Topic.js
  8. 2
      app/src/containers/TopicContainer.js
  9. 8
      app/src/containers/TopicList.js
  10. 4
      app/src/containers/UsernameFormContainer.js

28
app/src/CustomPropTypes.js

@ -2,11 +2,27 @@ import PropTypes from 'prop-types';
//TODO: Move this file //TODO: Move this file
const GetTopicResult = PropTypes.PropTypes.shape({ const GetTopicResult = PropTypes.PropTypes.shape({
0: PropTypes.string, userAddress: PropTypes.string.isRequired,
1: PropTypes.string, fullOrbitAddress: PropTypes.string.isRequired,
2: PropTypes.string, userName: PropTypes.string.isRequired,
3: PropTypes.string, timestamp: PropTypes.number.isRequired,
4: PropTypes.arrayOf(PropTypes.number) numberOfReplies: PropTypes.number.isRequired
}); });
export { GetTopicResult }; const GetPostResult = PropTypes.PropTypes.shape({
userAddress: PropTypes.string.isRequired,
fullOrbitAddress: PropTypes.string.isRequired,
userName: PropTypes.string.isRequired,
timestamp: PropTypes.number.isRequired,
topicID: PropTypes.string.isRequired
});
const TopicPlaceholderExtra = PropTypes.PropTypes.shape({
topicID: PropTypes.number.isRequired,
});
const PostPlaceholderExtra = PropTypes.PropTypes.shape({
postIndex: PropTypes.number.isRequired,
});
export { GetTopicResult, GetPostResult, TopicPlaceholderExtra, PostPlaceholderExtra };

164
app/src/containers/PlaceholderContainer.js

@ -0,0 +1,164 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withRouter } from 'react-router-dom';
import { TopicPlaceholderExtra, PostPlaceholderExtra } from '../CustomPropTypes'
import ContentLoader from 'react-content-loader';
import { Card, Button, Divider, Grid, Icon, Label } from 'semantic-ui-react';
class PlaceholderContainer extends Component {
render() {
const { placeholderType, extra, history } = this.props;
switch (placeholderType) {
case 'Topic':
return(
<Card
link
className="card"
onClick={() => {
history.push(`/topic/${extra.topicID}`);
}}
>
<Card.Content>
<div>
<ContentLoader
height={5.8}
width={300}
speed={2}
primaryColor="#b2e8e6"
secondaryColor="#00b5ad"
>
<rect x="0" y="0" rx="3" ry="3" width="150" height="5.5" />
</ContentLoader>
</div>
<hr />
<div className="topic-meta">
<p className="no-margin">
<ContentLoader
height={5.8}
width={300}
speed={2}
primaryColor="#b2e8e6"
secondaryColor="#00b5ad"
>
<rect x="0" y="0" rx="3" ry="3" width="60" height="5.5" />
</ContentLoader>
</p>
<p className="no-margin">
<ContentLoader
height={5.8}
width={300}
speed={2}
primaryColor="#b2e8e6"
secondaryColor="#00b5ad"
>
<rect x="0" y="0" rx="3" ry="3" width="70" height="5.5" />
</ContentLoader>
</p>
<p className="topic-date grey-text">
<ContentLoader
height={5.8}
width={300}
speed={2}
primaryColor="#b2e8e6"
secondaryColor="#00b5ad"
>
<rect x="260" y="0" rx="3" ry="3" width="40" height="5.5" />
</ContentLoader>
</p>
</div>
</Card.Content>
</Card>
);
case 'Post':
return(
<div className="post">
<Divider horizontal>
<span className="grey-text">
#
{extra.postIndex}
</span>
</Divider>
<Grid>
<Grid.Row columns={16} stretched>
<Grid.Column width={1} className="user-avatar">
<div className="user-avatar">
<ContentLoader
height={52}
width={52}
speed={2}
primaryColor="#b2e8e6"
secondaryColor="#00b5ad"
>
<circle cx="26" cy="26" r="26" />
</ContentLoader>
</div>
</Grid.Column>
<Grid.Column width={15}>
<div className="">
<div className="stretch-space-between">
<span className='grey-text'>
<strong>Username</strong>
</span>
<span className="grey-text">
<ContentLoader height={5.8} width={300} speed={2} primaryColor="#b2e8e6"
secondaryColor="#00b5ad" >
<rect x="280" y="0" rx="3" ry="3" width="20" height="5.5" />
</ContentLoader>
</span>
</div>
<div className="stretch-space-between">
<span className='grey-text' >
<strong>
<ContentLoader height={5.8} width={300} speed={2} primaryColor="#b2e8e6"
secondaryColor="#00b5ad" >
<rect x="0" y="0" rx="3" ry="3" width="75" height="5.5" />
</ContentLoader>
</strong>
</span>
</div>
<div className="post-content">
<ContentLoader height={11.2} width={300} speed={2}
primaryColor="#b2e8e6" secondaryColor="#00b5ad" >
<rect x="0" y="0" rx="3" ry="3" width="180" height="4.0" />
<rect x="0" y="6.5" rx="3" ry="3" width="140" height="4.0" />
</ContentLoader>
</div>
</div>
</Grid.Column>
</Grid.Row>
<Grid.Row>
<Grid.Column floated="right" textAlign="right">
<Button icon size="mini" disabled
style={{
marginRight: '0px'
}}
>
<Icon name="chevron up" />
</Button>
<Label color="teal">Loading...</Label>
<Button icon size="mini" disabled >
<Icon name="chevron down" />
</Button>
<Button icon size="mini" disabled >
<Icon name="linkify" />
</Button>
</Grid.Column>
</Grid.Row>
</Grid>
</div>
);
}
}
}
PlaceholderContainer.propTypes = {
placeholderType: PropTypes.string.isRequired,
extra: PropTypes.oneOfType([
TopicPlaceholderExtra.isRequired,
PostPlaceholderExtra.isRequired
])
};
export default withRouter(PlaceholderContainer);

88
app/src/containers/Post.js

@ -4,6 +4,7 @@ import { bindActionCreators } from 'redux';
import { push } from 'connected-react-router'; import { push } from 'connected-react-router';
import { Link, withRouter } from 'react-router-dom'; import { Link, withRouter } from 'react-router-dom';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { GetPostResult } from '../CustomPropTypes'
import ContentLoader from 'react-content-loader'; import ContentLoader from 'react-content-loader';
import { Button, Divider, Grid, Icon, Label, Transition } from 'semantic-ui-react'; import { Button, Divider, Grid, Icon, Label, Transition } from 'semantic-ui-react';
@ -65,7 +66,7 @@ class Post extends Component {
const { fetchPostDataStatus } = this.state; const { fetchPostDataStatus } = this.state;
const { postData, orbitDB, postID } = this.props; const { postData, orbitDB, postID } = this.props;
if (postData && orbitDB.orbitdb && fetchPostDataStatus === 'pending') { if (orbitDB.orbitdb && fetchPostDataStatus === 'pending') {
this.setState({ this.setState({
fetchPostDataStatus: 'fetching' fetchPostDataStatus: 'fetching'
}); });
@ -77,10 +78,10 @@ class Post extends Component {
const { address, postData, orbitDB } = this.props; const { address, postData, orbitDB } = this.props;
let orbitPostData; let orbitPostData;
if (postData.value[1] === address) { if (postData.userAddress === address) {
orbitPostData = orbitDB.postsDB.get(postID); orbitPostData = orbitDB.postsDB.get(postID);
} else { } else {
const fullAddress = `/orbitdb/${postData.value[0]}/posts`; const fullAddress = `/orbitdb/${postData.fullOrbitAddress}/posts`;
const store = await orbitDB.orbitdb.keyvalue(fullAddress); const store = await orbitDB.orbitdb.keyvalue(fullAddress);
await store.load(); await store.load();
@ -107,30 +108,6 @@ class Post extends Component {
const { animateOnToggle, postSubject, postContent } = this.state; const { animateOnToggle, postSubject, postContent } = this.state;
const { avatarUrl, postIndex, navigateTo, postData, postID } = this.props; const { avatarUrl, postIndex, navigateTo, postData, postID } = this.props;
const avatarView = (postData
? (
<UserAvatar
size="52"
className="inline"
src={avatarUrl}
name={postData.value[2]}
/>
)
: (
<div className="user-avatar">
<ContentLoader
height={52}
width={52}
speed={2}
primaryColor="#b2e8e6"
secondaryColor="#00b5ad"
>
<circle cx="26" cy="26" r="26" />
</ContentLoader>
</div>
)
);
return ( return (
<Transition <Transition
animation="tada" animation="tada"
@ -147,48 +124,31 @@ class Post extends Component {
<Grid> <Grid>
<Grid.Row columns={16} stretched> <Grid.Row columns={16} stretched>
<Grid.Column width={1} className="user-avatar"> <Grid.Column width={1} className="user-avatar">
{postData !== null <Link
? ( to={`/profile/${postData.userAddress}/${postData.userName}`}
<Link onClick={(event) => { event.stopPropagation(); }} >
to={`/profile/${postData.value[1] <UserAvatar
}/${postData.value[2]}`} size="52"
onClick={(event) => { event.stopPropagation(); }} className="inline"
> src={avatarUrl}
{avatarView} name={postData.userName}
</Link> />
) </Link>
: avatarView
}
</Grid.Column> </Grid.Column>
<Grid.Column width={15}> <Grid.Column width={15}>
<div className=""> <div className="">
<div className="stretch-space-between"> <div className="stretch-space-between">
<span className={postData <span>
!== null ? '' : 'grey-text'}
>
<strong> <strong>
{postData !== null {postData.userName}
? postData.value[2]
: 'Username'
}
</strong> </strong>
</span> </span>
<span className="grey-text"> <span className="grey-text">
{postData !== null <TimeAgo date={postData.timestamp} />
&& (
<TimeAgo date={
postData.value[3]*1000
}
/>
)
}
</span> </span>
</div> </div>
<div className="stretch-space-between"> <div className="stretch-space-between">
<span <span>
className={postSubject
=== '' ? '' : 'grey-text'}
>
<strong> <strong>
{postSubject === '' {postSubject === ''
? ( ? (
@ -209,9 +169,8 @@ class Post extends Component {
/> />
</ContentLoader> </ContentLoader>
) )
: `Subject: ${ : `Subject: ${postSubject}`
postSubject}` }
}
</strong> </strong>
</span> </span>
</div> </div>
@ -243,8 +202,7 @@ class Post extends Component {
height="4.0" height="4.0"
/> />
</ContentLoader> </ContentLoader>
) )}
}
</div> </div>
</div> </div>
</Grid.Column> </Grid.Column>
@ -270,7 +228,7 @@ class Post extends Component {
onClick={postData onClick={postData
? () => { ? () => {
navigateTo(`/topic/${ navigateTo(`/topic/${
postData.value[4]}/${ postData.topicID}/${
postID}`); postID}`);
} }
: () => {}} : () => {}}
@ -293,7 +251,7 @@ Post.propTypes = {
avatarUrl: PropTypes.string, avatarUrl: PropTypes.string,
postIndex: PropTypes.number.isRequired, postIndex: PropTypes.number.isRequired,
navigateTo: PropTypes.func.isRequired, navigateTo: PropTypes.func.isRequired,
postData: PropTypes.object, postData: GetPostResult.isRequired,
postID: PropTypes.string.isRequired postID: PropTypes.string.isRequired
}; };

44
app/src/containers/PostList.js

@ -4,6 +4,7 @@ import { connect } from 'react-redux';
import { drizzle } from '../index'; import { drizzle } from '../index';
import Post from './Post'; import Post from './Post';
import PlaceholderContainer from './PlaceholderContainer';
const contract = 'Forum'; const contract = 'Forum';
const getPostMethod = 'getPost'; const getPostMethod = 'getPost';
@ -56,19 +57,34 @@ class PostList extends Component {
const { dataKeys } = this.state; const { dataKeys } = this.state;
const { postIDs, contracts, focusOnPost, recentToTheTop } = this.props; const { postIDs, contracts, focusOnPost, recentToTheTop } = this.props;
const posts = postIDs.map((postID, index) => ( const posts = postIDs.map((postID, index) => {
<Post let fetchedPostData;
postData={(dataKeys[postID] if(dataKeys[postID])
&& contracts[contract][getPostMethod][dataKeys[postID]]) fetchedPostData = contracts[contract][getPostMethod][dataKeys[postID]];
? contracts[contract][getPostMethod][dataKeys[postID]]
: null} if(fetchedPostData) {
avatarUrl="" const postData = {
postIndex={index} userAddress: fetchedPostData.value[1],
postID={postID} fullOrbitAddress: `/orbitdb/${fetchedPostData.value[0]}/posts`,
getFocus={focusOnPost === postID} userName: fetchedPostData.value[2],
key={postID} timestamp: fetchedPostData.value[3]*1000,
/> topicID: fetchedPostData.value[4]
)); };
return(
<Post
postData={postData}
avatarUrl=""
postIndex={index}
postID={postID}
getFocus={focusOnPost === postID}
key={postID}
/>
)
}
return (<PlaceholderContainer placeholderType='Post'
extra={{postIndex: index}} key={postID} />);
});
return ( return (
<div> <div>
@ -84,7 +100,7 @@ class PostList extends Component {
PostList.propTypes = { PostList.propTypes = {
drizzleStatus: PropTypes.object.isRequired, drizzleStatus: PropTypes.object.isRequired,
postIDs: PropTypes.array.isRequired, postIDs: PropTypes.array.isRequired,
contracts: PropTypes.array.isRequired, contracts: PropTypes.PropTypes.objectOf(PropTypes.object).isRequired,
focusOnPost: PropTypes.number, focusOnPost: PropTypes.number,
recentToTheTop: PropTypes.bool recentToTheTop: PropTypes.bool
}; };

6
app/src/containers/ProfileContainer.js

@ -136,8 +136,8 @@ class ProfileContainer extends Component {
<ProfileInformation <ProfileInformation
address={userAddress} address={userAddress}
username={username} username={username}
numberOfTopics={topicIDs && topicIDs.length} numberOfTopics={topicIDs ? topicIDs.length : -1}
numberOfPosts={postIDs && postIDs.length} numberOfPosts={postIDs ? postIDs.length : -1}
self={userAddress === user.address} self={userAddress === user.address}
key="profileInfo" key="profileInfo"
/> />
@ -200,7 +200,7 @@ class ProfileContainer extends Component {
ProfileContainer.propTypes = { ProfileContainer.propTypes = {
match: PropTypes.object.isRequired, match: PropTypes.object.isRequired,
drizzleStatus: PropTypes.object.isRequired, drizzleStatus: PropTypes.object.isRequired,
contracts: PropTypes.array.isRequired, contracts: PropTypes.PropTypes.objectOf(PropTypes.object).isRequired,
navigateTo: PropTypes.func.isRequired, navigateTo: PropTypes.func.isRequired,
user: PropTypes.object.isRequired, user: PropTypes.object.isRequired,
setNavBarTitle: PropTypes.func.isRequired setNavBarTitle: PropTypes.func.isRequired

106
app/src/containers/ProfileInformation.js

@ -6,7 +6,9 @@ import { drizzle } from '../index';
import epochTimeConverter from '../helpers/EpochTimeConverter'; import epochTimeConverter from '../helpers/EpochTimeConverter';
import ContentLoader from 'react-content-loader';
import UsernameFormContainer from './UsernameFormContainer'; import UsernameFormContainer from './UsernameFormContainer';
import { Table } from 'semantic-ui-react'
const callsInfo = [ const callsInfo = [
{ {
@ -130,46 +132,76 @@ class ProfileInformation extends Component {
name={username} name={username}
/> />
)} )}
<table className="highlight centered responsive-table"> <Table basic='very' singleLine>
<tbody> <Table.Body>
<tr> <Table.Row>
<td><strong>Username:</strong></td> <Table.Cell><strong>Username:</strong></Table.Cell>
<td>{username}</td> <Table.Cell>{username}</Table.Cell>
</tr> </Table.Row>
<tr> <Table.Row>
<td><strong>Account address:</strong></td> <Table.Cell><strong>Account address:</strong></Table.Cell>
<td>{address}</td> <Table.Cell>{address}</Table.Cell>
</tr> </Table.Row>
<tr> <Table.Row>
<td><strong>OrbitDB:</strong></td> <Table.Cell><strong>OrbitDB:</strong></Table.Cell>
<td>{orbitDBId}</td> <Table.Cell>{orbitDBId ? orbitDBId
</tr> : <ContentLoader height={5.8} width={300} speed={2}
<tr> primaryColor="#b2e8e6" secondaryColor="#00b5ad"
<td><strong>TopicsDB:</strong></td> >
<td>{topicsDBId}</td> <rect x="0" y="0" rx="3" ry="3" width="80" height="5.5" />
</tr> </ContentLoader>
<tr> }</Table.Cell>
<td><strong>PostsDB:</strong></td> </Table.Row>
<td>{postsDBId}</td> <Table.Row>
</tr> <Table.Cell><strong>TopicsDB:</strong></Table.Cell>
<tr> <Table.Cell>{topicsDBId ? topicsDBId
<td><strong>Number of topics created:</strong></td> : <ContentLoader height={5.8} width={300} speed={2}
<td>{numberOfTopics}</td> primaryColor="#b2e8e6" secondaryColor="#00b5ad"
</tr> >
<tr> <rect x="0" y="0" rx="3" ry="3" width="80" height="5.5" />
<td><strong>Number of posts:</strong></td> </ContentLoader>
<td>{numberOfPosts}</td> }</Table.Cell>
</tr> </Table.Row>
<Table.Row>
<Table.Cell><strong>PostsDB:</strong></Table.Cell>
<Table.Cell>{postsDBId ? postsDBId
: <ContentLoader height={5.8} width={300} speed={2}
primaryColor="#b2e8e6" secondaryColor="#00b5ad"
>
<rect x="0" y="0" rx="3" ry="3" width="80" height="5.5" />
</ContentLoader>
}</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell><strong>Number of topics created:</strong></Table.Cell>
<Table.Cell>{numberOfTopics !== -1 ? numberOfTopics
: <ContentLoader height={5.8} width={300} speed={2}
primaryColor="#b2e8e6" secondaryColor="#00b5ad"
>
<rect x="0" y="0" rx="3" ry="3" width="15" height="5.5" />
</ContentLoader>
}</Table.Cell>
</Table.Row>
<Table.Row>
<Table.Cell><strong>Number of posts:</strong></Table.Cell>
<Table.Cell>{numberOfPosts !== -1 ? numberOfPosts
: <ContentLoader height={5.8} width={300} speed={2}
primaryColor="#b2e8e6" secondaryColor="#00b5ad"
>
<rect x="0" y="0" rx="3" ry="3" width="15" height="5.5" />
</ContentLoader>
}</Table.Cell>
</Table.Row>
{dateOfRegister {dateOfRegister
&& ( && (
<tr> <Table.Row>
<td><strong>Member since:</strong></td> <Table.Cell><strong>Member since:</strong></Table.Cell>
<td>{epochTimeConverter(dateOfRegister)}</td> <Table.Cell>{epochTimeConverter(dateOfRegister)}</Table.Cell>
</tr> </Table.Row>
) )
} }
</tbody> </Table.Body>
</table> </Table>
{self && <UsernameFormContainer />} {self && <UsernameFormContainer />}
</div> </div>
); );
@ -178,7 +210,7 @@ class ProfileInformation extends Component {
ProfileInformation.propTypes = { ProfileInformation.propTypes = {
drizzleStatus: PropTypes.object.isRequired, drizzleStatus: PropTypes.object.isRequired,
contracts: PropTypes.array.isRequired, contracts: PropTypes.PropTypes.objectOf(PropTypes.object).isRequired,
avatarUrl: PropTypes.string, avatarUrl: PropTypes.string,
username: PropTypes.string.isRequired, username: PropTypes.string.isRequired,
address: PropTypes.string.isRequired, address: PropTypes.string.isRequired,

5
app/src/containers/Topic.js

@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { GetTopicResult } from '../CustomPropTypes'
import ContentLoader from 'react-content-loader'; import ContentLoader from 'react-content-loader';
import { Card } from 'semantic-ui-react'; import { Card } from 'semantic-ui-react';
@ -54,7 +55,7 @@ class Topic extends Component {
{topicData.userName} {topicData.userName}
</p> </p>
<p className="no-margin"> <p className="no-margin">
Number of Replies: {topicData.nReplies} Number of Replies: {topicData.numberOfReplies}
</p> </p>
<p className="topic-date grey-text"> <p className="topic-date grey-text">
<TimeAgo date={topicData.timestamp}/> <TimeAgo date={topicData.timestamp}/>
@ -69,7 +70,7 @@ class Topic extends Component {
Topic.propTypes = { Topic.propTypes = {
userAddress: PropTypes.string.isRequired, userAddress: PropTypes.string.isRequired,
history: PropTypes.object.isRequired, history: PropTypes.object.isRequired,
//TODO: topicData: GetTopicResult.isRequired, topicData: GetTopicResult.isRequired,
orbitDB: PropTypes.object.isRequired, orbitDB: PropTypes.object.isRequired,
topicID: PropTypes.number.isRequired topicID: PropTypes.number.isRequired
}; };

2
app/src/containers/TopicContainer.js

@ -191,7 +191,7 @@ TopicContainer.propTypes = {
drizzleStatus: PropTypes.object.isRequired, drizzleStatus: PropTypes.object.isRequired,
orbitDB: PropTypes.object.isRequired, orbitDB: PropTypes.object.isRequired,
setNavBarTitle: PropTypes.func.isRequired, setNavBarTitle: PropTypes.func.isRequired,
contracts: PropTypes.array.isRequired, contracts: PropTypes.PropTypes.objectOf(PropTypes.object).isRequired,
user: PropTypes.object.isRequired, user: PropTypes.object.isRequired,
match: PropTypes.object.isRequired, match: PropTypes.object.isRequired,
navigateTo: PropTypes.func.isRequired navigateTo: PropTypes.func.isRequired

8
app/src/containers/TopicList.js

@ -4,6 +4,7 @@ import { connect } from 'react-redux';
import { drizzle } from '../index'; import { drizzle } from '../index';
import Topic from './Topic'; import Topic from './Topic';
import PlaceholderContainer from './PlaceholderContainer';
const contract = 'Forum'; const contract = 'Forum';
const getTopicMethod = 'getTopic'; const getTopicMethod = 'getTopic';
@ -62,11 +63,11 @@ class TopicList extends Component {
if(fetchedTopicData) { if(fetchedTopicData) {
const topicData = { const topicData = {
userAddress: fetchedTopicData.value[0], userAddress: fetchedTopicData.value[1],
fullOrbitAddress: `/orbitdb/${fetchedTopicData.value[0]}/topics`, fullOrbitAddress: `/orbitdb/${fetchedTopicData.value[0]}/topics`,
userName: fetchedTopicData.value[2], userName: fetchedTopicData.value[2],
timestamp: fetchedTopicData.value[3]*1000, timestamp: fetchedTopicData.value[3]*1000,
nReplies: fetchedTopicData.value[4].length numberOfReplies: fetchedTopicData.value[4].length
}; };
return( return(
<Topic <Topic
@ -77,7 +78,8 @@ class TopicList extends Component {
) )
} }
return (<div key={topicID}>TODO: Loading UI/fetching needs to be changed (?)</div>); return (<PlaceholderContainer placeholderType='Topic'
extra={{topicID: topicID}} key={topicID} />);
}); });
//TODO: Return loading indicator instead of topics when not fully loaded (?) //TODO: Return loading indicator instead of topics when not fully loaded (?)

4
app/src/containers/UsernameFormContainer.js

@ -213,8 +213,8 @@ UsernameFormContainer.propTypes = {
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
account: PropTypes.string.isRequired, account: PropTypes.string.isRequired,
transactionStack: PropTypes.array.isRequired, transactionStack: PropTypes.array.isRequired,
transactions: PropTypes.array.isRequired, transactions: PropTypes.PropTypes.objectOf(PropTypes.object).isRequired,
contracts: PropTypes.array.isRequired, contracts: PropTypes.PropTypes.objectOf(PropTypes.object).isRequired,
user: PropTypes.object.isRequired user: PropTypes.object.isRequired
}; };

Loading…
Cancel
Save