mirror of https://gitlab.com/ecentrics/concordia
Apostolos Fanakis
6 years ago
13 changed files with 661 additions and 17 deletions
@ -0,0 +1,175 @@ |
|||||
|
import React, { Component } from 'react'; |
||||
|
import { connect } from 'react-redux'; |
||||
|
|
||||
|
import { Grid, Form, TextArea, Button, Icon, Divider } from 'semantic-ui-react' |
||||
|
|
||||
|
import TimeAgo from 'react-timeago'; |
||||
|
import UserAvatar from 'react-user-avatar'; |
||||
|
import ReactMarkdown from 'react-markdown'; |
||||
|
|
||||
|
/*import { createPost } from '../redux/actions/transactionsMonitorActions';*/ |
||||
|
|
||||
|
class NewPost extends Component { |
||||
|
constructor(props, context) { |
||||
|
super(props); |
||||
|
|
||||
|
this.handleInputChange = this.handleInputChange.bind(this); |
||||
|
this.handlePreviewToggle = this.handlePreviewToggle.bind(this); |
||||
|
this.validateAndPost = this.validateAndPost.bind(this); |
||||
|
|
||||
|
this.newPostOuterRef = React.createRef(); |
||||
|
|
||||
|
this.state = { |
||||
|
postSubjectInput: this.props.subject ? this.props.subject : "", |
||||
|
postContentInput: '', |
||||
|
postSubjectInputEmptySubmit: false, |
||||
|
postContentInputEmptySubmit: false, |
||||
|
previewEnabled: false, |
||||
|
previewDate: "" |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
async validateAndPost() { |
||||
|
if (this.state.postSubjectInput === '' || this.state.postContentInput === ''){ |
||||
|
this.setState({ |
||||
|
postSubjectInputEmptySubmit: this.state.postSubjectInput === '', |
||||
|
postContentInputEmptySubmit: this.state.postContentInput === '' |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
/*this.props.store.dispatch( |
||||
|
createPost(this.props.topicID, |
||||
|
{ |
||||
|
postSubject: this.state.postSubjectInput, |
||||
|
postMessage: this.state.postContentInput |
||||
|
} |
||||
|
) |
||||
|
);*/ |
||||
|
this.props.onPostCreated(); |
||||
|
} |
||||
|
|
||||
|
handleInputChange(event) { |
||||
|
this.setState({[event.target.name]: event.target.value}); |
||||
|
} |
||||
|
|
||||
|
handlePreviewToggle() { |
||||
|
this.setState((prevState, props) => ({ |
||||
|
previewEnabled: !prevState.previewEnabled, |
||||
|
previewDate: this.getDate() |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
getDate() { |
||||
|
const currentdate = new Date(); |
||||
|
return ((currentdate.getMonth() + 1) + " " |
||||
|
+ currentdate.getDate() + ", " |
||||
|
+ currentdate.getFullYear() + ", " |
||||
|
+ currentdate.getHours() + ":" |
||||
|
+ currentdate.getMinutes() + ":" |
||||
|
+ currentdate.getSeconds()); |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
return ( |
||||
|
<div className="post" ref={this.newPostOuterRef}> |
||||
|
<Divider horizontal> |
||||
|
<span className="grey-text">#{this.props.postIndex}</span> |
||||
|
</Divider> |
||||
|
<Grid> |
||||
|
<Grid.Row columns={16} stretched> |
||||
|
<Grid.Column width={1} className="user-avatar"> |
||||
|
<UserAvatar |
||||
|
size="52" |
||||
|
className="inline user-avatar" |
||||
|
src={this.props.avatarUrl} |
||||
|
name={this.props.user.username} |
||||
|
/> |
||||
|
</Grid.Column> |
||||
|
<Grid.Column width={15}> |
||||
|
<div className=""> |
||||
|
<div className="stretch-space-between"> |
||||
|
<span><strong>{this.props.user.username}</strong></span> |
||||
|
<span className="grey-text"> |
||||
|
{this.state.previewEnabled && |
||||
|
<TimeAgo date={this.state.previewDate}/> |
||||
|
} |
||||
|
</span> |
||||
|
</div> |
||||
|
<div className="stretch-space-between"> |
||||
|
<span><strong> |
||||
|
{this.state.previewEnabled && |
||||
|
("Subject: " + this.state.postSubjectInput) |
||||
|
} |
||||
|
</strong></span> |
||||
|
</div> |
||||
|
<div className="post-content"> |
||||
|
<div style={{display: this.state.previewEnabled ? "block" : "none"}}> |
||||
|
<ReactMarkdown source={this.state.postContentInput} |
||||
|
className="markdown-preview" /> |
||||
|
</div> |
||||
|
<Form className="topic-form"> |
||||
|
<Form.Input key={"postSubjectInput"} |
||||
|
style={{display: this.state.previewEnabled ? "none" : ""}} |
||||
|
name={"postSubjectInput"} |
||||
|
error={this.state.postSubjectInputEmptySubmit} |
||||
|
type="text" |
||||
|
value={this.state.postSubjectInput} |
||||
|
placeholder="Subject" |
||||
|
id="postSubjectInput" |
||||
|
onChange={this.handleInputChange} /> |
||||
|
<TextArea key={"postContentInput"} |
||||
|
style={{display: this.state.previewEnabled ? "none" : ""}} |
||||
|
name={"postContentInput"} |
||||
|
className={this.state.postContentInputEmptySubmit ? "form-textarea-required" : ""} |
||||
|
value={this.state.postContentInput} |
||||
|
placeholder="Post" |
||||
|
id="postContentInput" |
||||
|
onChange={this.handleInputChange} |
||||
|
rows={4} autoHeight /> |
||||
|
<br/><br/> |
||||
|
<Button.Group> |
||||
|
<Button key="submit" |
||||
|
type="button" |
||||
|
onClick={this.validateAndPost} |
||||
|
color='teal' |
||||
|
animated> |
||||
|
<Button.Content visible>Post</Button.Content> |
||||
|
<Button.Content hidden> |
||||
|
<Icon name='reply' /> |
||||
|
</Button.Content> |
||||
|
</Button> |
||||
|
<Button type="button" |
||||
|
onClick={this.handlePreviewToggle} |
||||
|
color='yellow'> |
||||
|
{this.state.previewEnabled ? "Edit" : "Preview"} |
||||
|
</Button> |
||||
|
<Button type="button" |
||||
|
onClick={this.props.onCancelClick} |
||||
|
color='red'> |
||||
|
Cancel |
||||
|
</Button> |
||||
|
</Button.Group> |
||||
|
</Form> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Grid.Column> |
||||
|
</Grid.Row> |
||||
|
</Grid> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
componentDidMount(){ |
||||
|
this.newPostOuterRef.current.scrollIntoView(true); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
orbitDB: state.orbitDB, |
||||
|
user: state.user |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default connect(mapStateToProps)(NewPost); |
@ -0,0 +1,193 @@ |
|||||
|
import React, { Component } from 'react'; |
||||
|
import { bindActionCreators } from 'redux'; |
||||
|
import { push } from 'connected-react-router' |
||||
|
import { Link, withRouter } from 'react-router-dom'; |
||||
|
import { connect } from 'react-redux'; |
||||
|
|
||||
|
import ContentLoader from "react-content-loader" |
||||
|
import { Transition } from 'semantic-ui-react' |
||||
|
import { Grid, Divider, Button, Icon, Label } from 'semantic-ui-react' |
||||
|
|
||||
|
import TimeAgo from 'react-timeago'; |
||||
|
import epochTimeConverter from '../helpers/EpochTimeConverter'; |
||||
|
import UserAvatar from 'react-user-avatar'; |
||||
|
import ReactMarkdown from 'react-markdown'; |
||||
|
|
||||
|
class Post extends Component { |
||||
|
constructor(props) { |
||||
|
super(props); |
||||
|
|
||||
|
this.fetchPost = this.fetchPost.bind(this); |
||||
|
if (props.getFocus){ |
||||
|
this.postRef = React.createRef(); |
||||
|
} |
||||
|
|
||||
|
this.state = { |
||||
|
fetchPostDataStatus: 'pending', |
||||
|
postContent: '', |
||||
|
postSubject: '', |
||||
|
readyForAnimation: false, |
||||
|
animateOnToggle: true |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async fetchPost(postID) { |
||||
|
let orbitPostData; |
||||
|
if (this.props.postData.value[1] === this.props.user.address) { |
||||
|
orbitPostData = this.props.orbitDB.postsDB.get(postID); |
||||
|
} else { |
||||
|
const fullAddress = "/orbitdb/" + this.props.postData.value[0] + "/posts"; |
||||
|
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress); |
||||
|
await store.load(); |
||||
|
|
||||
|
let localOrbitData = store.get(postID); |
||||
|
if (localOrbitData) { |
||||
|
orbitPostData = localOrbitData; |
||||
|
} else { |
||||
|
// Wait until we have received something from the network
|
||||
|
store.events.on('replicated', () => { |
||||
|
orbitPostData = store.get(postID); |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.setState({ |
||||
|
postContent: orbitPostData.content, |
||||
|
postSubject: orbitPostData.subject, |
||||
|
fetchPostDataStatus: 'fetched', |
||||
|
readyForAnimation: true |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
render(){ |
||||
|
let avatarView = (this.props.postData |
||||
|
? <UserAvatar |
||||
|
size="52" |
||||
|
className="inline" |
||||
|
src={this.props.avatarUrl} |
||||
|
name={this.props.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 ( |
||||
|
<Transition animation='tada' duration={500} visible={this.state.animateOnToggle}> |
||||
|
<div className="post" ref={this.postRef ? this.postRef : null}> |
||||
|
<Divider horizontal> |
||||
|
<span className="grey-text">#{this.props.postIndex}</span> |
||||
|
</Divider> |
||||
|
<Grid> |
||||
|
<Grid.Row columns={16} stretched> |
||||
|
<Grid.Column width={1} className="user-avatar"> |
||||
|
{this.props.postData !== null |
||||
|
?<Link to={"/profile/" + this.props.postData.value[1] |
||||
|
+ "/" + this.props.postData.value[2]} |
||||
|
onClick={(event) => {event.stopPropagation()}}> |
||||
|
{avatarView} |
||||
|
</Link> |
||||
|
:avatarView |
||||
|
} |
||||
|
</Grid.Column> |
||||
|
<Grid.Column width={15}> |
||||
|
<div className=""> |
||||
|
<div className="stretch-space-between"> |
||||
|
<span className={this.props.postData !== null ? "" : "grey-text"}> |
||||
|
<strong> |
||||
|
{this.props.postData !== null |
||||
|
?this.props.postData.value[2] |
||||
|
:"Username" |
||||
|
} |
||||
|
</strong> |
||||
|
</span> |
||||
|
<span className="grey-text"> |
||||
|
{this.props.postData !== null && |
||||
|
<TimeAgo date={epochTimeConverter(this.props.postData.value[3])}/> |
||||
|
} |
||||
|
</span> |
||||
|
</div> |
||||
|
<div className="stretch-space-between"> |
||||
|
<span className={this.state.postSubject === '' ? "" : "grey-text"}> |
||||
|
<strong> |
||||
|
{this.state.postSubject === '' |
||||
|
? <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> |
||||
|
: 'Subject:' + this.state.postSubject |
||||
|
} |
||||
|
</strong> |
||||
|
</span> |
||||
|
</div> |
||||
|
<div className="post-content"> |
||||
|
{this.state.postContent !== '' |
||||
|
? <ReactMarkdown source={this.state.postContent} /> |
||||
|
: <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' style={{marginRight: "0px"}}> |
||||
|
<Icon name='chevron up' /> |
||||
|
</Button> |
||||
|
<Label color="teal">8000</Label> |
||||
|
<Button icon size='mini'> |
||||
|
<Icon name='chevron down' /> |
||||
|
</Button> |
||||
|
<Button icon size='mini' |
||||
|
onClick={this.props.postData |
||||
|
? () => { this.props.navigateTo("/topic/" |
||||
|
+ this.props.postData.value[4] + "/" |
||||
|
+ this.props.postID)} |
||||
|
: () => {}}> |
||||
|
<Icon name='linkify' /> |
||||
|
</Button> |
||||
|
</Grid.Column> |
||||
|
</Grid.Row> |
||||
|
</Grid> |
||||
|
</div> |
||||
|
</Transition> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
componentDidUpdate() { |
||||
|
if (this.props.postData && this.state.fetchPostDataStatus === "pending") { |
||||
|
this.setState({ fetchPostDataStatus: 'fetching' }); |
||||
|
/*this.fetchPost(this.props.postID);*/ |
||||
|
} |
||||
|
if (this.state.readyForAnimation){ |
||||
|
if (this.postRef){ |
||||
|
setTimeout(() => { |
||||
|
this.postRef.current.scrollIntoView({ block: 'start', behavior: 'smooth' }); |
||||
|
setTimeout(() => { |
||||
|
this.setState({ animateOnToggle: false }); |
||||
|
}, 300); |
||||
|
}, 100); |
||||
|
this.setState({ readyForAnimation: false }); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const mapDispatchToProps = dispatch => bindActionCreators({ |
||||
|
navigateTo: (location) => push(location) |
||||
|
}, dispatch); |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
user: state.user, |
||||
|
orbitDB: state.orbit |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Post)); |
@ -0,0 +1,66 @@ |
|||||
|
import React, { Component } from 'react'; |
||||
|
import { connect } from 'react-redux'; |
||||
|
import { drizzle } from '../index'; |
||||
|
|
||||
|
import Post from './Post'; |
||||
|
|
||||
|
const contract = "Forum"; |
||||
|
const getPostMethod = "getPost"; |
||||
|
|
||||
|
class PostList extends Component { |
||||
|
constructor(props) { |
||||
|
super(props); |
||||
|
|
||||
|
this.dataKeys = []; |
||||
|
|
||||
|
if (this.props.drizzleStatus['initialized']){ |
||||
|
this.props.postIDs.forEach( postID => { |
||||
|
if (!this.dataKeys[postID]) { |
||||
|
this.dataKeys[postID] = drizzle.contracts[contract].methods[getPostMethod].cacheCall(postID); |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
componentDidUpdate(){ |
||||
|
if (this.props.drizzleStatus['initialized']){ |
||||
|
this.props.postIDs.forEach( postID => { |
||||
|
if (!this.dataKeys[postID]) { |
||||
|
this.dataKeys[postID] = drizzle.contracts[contract].methods[getPostMethod].cacheCall(postID); |
||||
|
} |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const posts = this.props.postIDs.map((postID, index) => { |
||||
|
return (<Post |
||||
|
postData={(this.dataKeys[postID] && this.props.contracts[contract][getPostMethod][this.dataKeys[postID]]) |
||||
|
? this.props.contracts[contract][getPostMethod][this.dataKeys[postID]] |
||||
|
: null} |
||||
|
avatarUrl={""} |
||||
|
postIndex={index} |
||||
|
postID={postID} |
||||
|
getFocus={this.props.focusOnPost === postID ? true : false} |
||||
|
key={postID} />) |
||||
|
}); |
||||
|
|
||||
|
return ( |
||||
|
<div> |
||||
|
{this.props.recentToTheTop |
||||
|
?posts.slice(0).reverse() |
||||
|
:posts |
||||
|
} |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
contracts: state.contracts, |
||||
|
drizzleStatus: state.drizzleStatus |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default connect(mapStateToProps)(PostList); |
@ -0,0 +1,156 @@ |
|||||
|
import React, { Component } from 'react'; |
||||
|
import { bindActionCreators } from 'redux'; |
||||
|
import { push } from 'connected-react-router' |
||||
|
import { connect } from 'react-redux'; |
||||
|
import { drizzle } from '../index'; |
||||
|
|
||||
|
import PostList from '../components/PostList'; |
||||
|
import NewPost from '../components/NewPost'; |
||||
|
import FloatingButton from '../components/FloatingButton'; |
||||
|
|
||||
|
import { setNavBarTitle } from '../redux/actions/userInterfaceActions.js'; |
||||
|
|
||||
|
const contract = "Forum"; |
||||
|
const getTopicMethod = "getTopic"; |
||||
|
|
||||
|
class TopicContainer extends Component { |
||||
|
constructor(props) { |
||||
|
super(props); |
||||
|
|
||||
|
//Topic ID should be a positive integer
|
||||
|
if (!/^[0-9]+$/.test(this.props.match.params.topicId)){ |
||||
|
this.props.navigateTo('/404'); |
||||
|
} |
||||
|
|
||||
|
this.fetchTopicSubject = this.fetchTopicSubject.bind(this); |
||||
|
this.togglePostingState = this.togglePostingState.bind(this); |
||||
|
this.postCreated = this.postCreated.bind(this); |
||||
|
|
||||
|
var pageStatus = 'initialized'; |
||||
|
if (this.props.drizzleStatus['initialized']) { |
||||
|
this.dataKey = drizzle.contracts[contract].methods[getTopicMethod].cacheCall(this.props.match.params.topicId); |
||||
|
pageStatus = 'loading'; |
||||
|
} |
||||
|
if (this.dataKey && this.props.contracts[contract][getTopicMethod][this.dataKey]) { |
||||
|
pageStatus = 'loaded'; |
||||
|
} |
||||
|
|
||||
|
this.state = { |
||||
|
pageStatus: pageStatus, |
||||
|
topicID: this.props.match.params.topicId, |
||||
|
topicSubject: null, |
||||
|
postFocus: this.props.match.params.postId && /^[0-9]+$/.test(this.props.match.params.postId) |
||||
|
? this.props.match.params.postId |
||||
|
: null, |
||||
|
fetchTopicSubjectStatus: null, |
||||
|
posting: false |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
componentDidUpdate() { |
||||
|
if (this.state.pageStatus === 'initialized' && |
||||
|
this.props.drizzleStatus['initialized']) { |
||||
|
this.dataKey = drizzle.contracts[contract].methods[getTopicMethod].cacheCall(this.state.topicId); |
||||
|
this.setState({ pageStatus: 'loading' }); |
||||
|
} |
||||
|
if (this.state.pageStatus === 'loading' && |
||||
|
this.props.contracts[contract][getTopicMethod][this.dataKey]) { |
||||
|
this.setState({ pageStatus: 'loaded' }); |
||||
|
if (this.state.fetchTopicSubjectStatus === null){ |
||||
|
this.setState({ fetchTopicSubjectStatus: "fetching"}) |
||||
|
/*this.fetchTopicSubject(this.props.contracts[contract][getTopicMethod][this.dataKey].value[0]);*/ |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async fetchTopicSubject(orbitDBAddress) { |
||||
|
let orbitData; |
||||
|
if (this.props.contracts[contract][getTopicMethod][this.dataKey].value[1] === this.props.user.address) { |
||||
|
orbitData = this.props.orbitDB.topicsDB.get(this.state.topicID); |
||||
|
} else { |
||||
|
const fullAddress = "/orbitdb/" + orbitDBAddress + "/topics"; |
||||
|
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress); |
||||
|
await store.load(); |
||||
|
|
||||
|
let localOrbitData = store.get(this.state.topicID); |
||||
|
if (localOrbitData) { |
||||
|
orbitData = localOrbitData; |
||||
|
} else { |
||||
|
// Wait until we have received something from the network
|
||||
|
store.events.on('replicated', () => { |
||||
|
orbitData = store.get(this.state.topicID); |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
this.props.setNavBarTitle(orbitData['subject']); |
||||
|
this.setState({ |
||||
|
'topicSubject': orbitData['subject'], |
||||
|
fetchTopicSubjectStatus: "fetched" |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
togglePostingState(event) { |
||||
|
if (event){ |
||||
|
event.preventDefault(); |
||||
|
} |
||||
|
this.setState(prevState => ({ |
||||
|
posting: !prevState.posting |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
postCreated(){ |
||||
|
this.setState(prevState => ({ |
||||
|
posting: false |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
var topicContents; |
||||
|
if (this.state.pageStatus === 'loaded') { |
||||
|
topicContents = ( |
||||
|
(<div> |
||||
|
<PostList postIDs={this.props.contracts[contract][getTopicMethod][this.dataKey].value[4]} |
||||
|
focusOnPost={this.state.postFocus ? this.state.postFocus : null}/> |
||||
|
{this.state.posting && |
||||
|
<NewPost topicID={this.state.topicID} |
||||
|
subject={this.state.topicSubject} |
||||
|
postIndex={this.props.contracts[contract][getTopicMethod][this.dataKey].value[4].length} |
||||
|
onCancelClick={() => {this.togglePostingState()}} |
||||
|
onPostCreated={() => {this.postCreated()}} |
||||
|
/> |
||||
|
} |
||||
|
<div className="posts-list-spacer"></div> |
||||
|
{this.props.user.hasSignedUp && !this.state.posting && |
||||
|
<FloatingButton onClick={this.togglePostingState}/> |
||||
|
} |
||||
|
</div>) |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<div className="fill"> |
||||
|
{topicContents} |
||||
|
{!this.state.posting && |
||||
|
<div className="bottom-overlay-pad"></div> |
||||
|
} |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const mapDispatchToProps = dispatch => bindActionCreators({ |
||||
|
navigateTo: (location) => push(location), |
||||
|
setNavBarTitle: (navBarTitle) => setNavBarTitle(navBarTitle) |
||||
|
}, dispatch); |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
user: state.user, |
||||
|
contracts: state.contracts, |
||||
|
drizzleStatus: state.drizzleStatus, |
||||
|
orbitDB: state.orbit |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default connect(mapStateToProps, mapDispatchToProps)(TopicContainer); |
@ -0,0 +1,10 @@ |
|||||
|
//Action creators
|
||||
|
|
||||
|
export const SET_NAVBAR_TITLE = 'SET_NAVBAR_TITLE'; |
||||
|
|
||||
|
export function setNavBarTitle(newTitle){ |
||||
|
return { |
||||
|
type: SET_NAVBAR_TITLE, |
||||
|
title: newTitle |
||||
|
}; |
||||
|
} |
@ -0,0 +1,20 @@ |
|||||
|
import { |
||||
|
SET_NAVBAR_TITLE |
||||
|
} from '../actions/userInterfaceActions'; |
||||
|
|
||||
|
const initialState = { |
||||
|
navBarTitle: '' |
||||
|
}; |
||||
|
|
||||
|
const userInterfaceReducer = (state = initialState, action) => { |
||||
|
switch (action.type) { |
||||
|
case SET_NAVBAR_TITLE: |
||||
|
return { |
||||
|
navBarTitle: action.title |
||||
|
} |
||||
|
default: |
||||
|
return state; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default userInterfaceReducer; |
Loading…
Reference in new issue