mirror of https://gitlab.com/ecentrics/concordia
Apostolos Fanakis
6 years ago
17 changed files with 677 additions and 24 deletions
@ -0,0 +1,14 @@ |
|||||
|
import React from 'react'; |
||||
|
import { Button, Icon } from 'semantic-ui-react' |
||||
|
|
||||
|
const FloatingButton = (props) => { |
||||
|
return ( |
||||
|
<div className="action-button" onClick={props.onClick}> |
||||
|
<Button icon color='teal' size='large'> |
||||
|
<Icon name='add'/> |
||||
|
</Button> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default FloatingButton; |
@ -0,0 +1,65 @@ |
|||||
|
import React, { Component } from 'react'; |
||||
|
import { connect } from 'react-redux'; |
||||
|
|
||||
|
import { Grid, Divider } from 'semantic-ui-react' |
||||
|
|
||||
|
import TimeAgo from 'react-timeago'; |
||||
|
import UserAvatar from 'react-user-avatar'; |
||||
|
import ReactMarkdown from 'react-markdown'; |
||||
|
|
||||
|
class Post extends Component { |
||||
|
constructor(props, context) { |
||||
|
super(props); |
||||
|
} |
||||
|
|
||||
|
render(){ |
||||
|
return ( |
||||
|
<div className="post"> |
||||
|
<Divider horizontal> |
||||
|
<span className="grey-text">#0</span> |
||||
|
</Divider> |
||||
|
<Grid> |
||||
|
<Grid.Row columns={16} stretched> |
||||
|
<Grid.Column width={1} className="user-avatar"> |
||||
|
<UserAvatar |
||||
|
size="52" |
||||
|
className="inline" |
||||
|
src={this.props.user.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"> |
||||
|
<TimeAgo date={this.props.date}/> |
||||
|
</span> |
||||
|
</div> |
||||
|
<div className="stretch-space-between"> |
||||
|
<span><strong> |
||||
|
Subject: {this.props.subject} |
||||
|
</strong></span> |
||||
|
</div> |
||||
|
<div className="post-content"> |
||||
|
<ReactMarkdown source={this.props.content} /> |
||||
|
</div> |
||||
|
</div> |
||||
|
</Grid.Column> |
||||
|
</Grid.Row> |
||||
|
</Grid> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
user: state.user |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default connect(mapStateToProps)(Post); |
@ -0,0 +1,104 @@ |
|||||
|
import React, { Component } from 'react'; |
||||
|
import { connect } from 'react-redux'; |
||||
|
import { withRouter } from 'react-router' |
||||
|
|
||||
|
import ContentLoader from "react-content-loader" |
||||
|
import { Card } from 'semantic-ui-react' |
||||
|
|
||||
|
import TimeAgo from 'react-timeago'; |
||||
|
import epochTimeConverter from '../helpers/EpochTimeConverter' |
||||
|
|
||||
|
class Topic extends Component { |
||||
|
constructor(props){ |
||||
|
super(props); |
||||
|
|
||||
|
this.fetchSubject = this.fetchSubject.bind(this); |
||||
|
|
||||
|
this.topicSubject = null; |
||||
|
this.topicSubjectFetchStatus = "pending"; |
||||
|
} |
||||
|
|
||||
|
async fetchSubject(topicID) { |
||||
|
this.topicSubjectFetchStatus = "fetching"; |
||||
|
|
||||
|
if (this.props.topicData.value[1] === this.props.user.address) { |
||||
|
let orbitData = this.props.orbitDB.topicsDB.get(topicID); |
||||
|
this.topicSubject = orbitData['subject']; |
||||
|
this.topicSubjectFetchStatus = "fetched"; |
||||
|
} else { |
||||
|
const fullAddress = "/orbitdb/" + this.props.topicData.value[0] + "/topics"; |
||||
|
const store = await this.props.orbitDB.orbitdb.keyvalue(fullAddress); |
||||
|
await store.load(); |
||||
|
|
||||
|
let localOrbitData = store.get(topicID); |
||||
|
if (localOrbitData) { |
||||
|
this.topicSubject = localOrbitData['subject']; |
||||
|
} else { |
||||
|
// Wait until we have received something from the network
|
||||
|
store.events.on('replicated', () => { |
||||
|
this.topicSubject = store.get(topicID)['subject']; |
||||
|
}) |
||||
|
} |
||||
|
this.topicSubjectFetchStatus = "fetched"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render(){ |
||||
|
return ( |
||||
|
<Card link className="card" |
||||
|
onClick={() => {this.props.history.push("/topic/" + this.props.topicID)}}> |
||||
|
<Card.Content> |
||||
|
<div className={"topic-subject" + (this.topicSubject ? "" : " grey-text")}> |
||||
|
<p><strong> |
||||
|
{this.topicSubject !== null ? this.topicSubject |
||||
|
:<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>} |
||||
|
</strong></p> |
||||
|
</div> |
||||
|
<hr/> |
||||
|
<div className="topic-meta"> |
||||
|
<p className={"no-margin" + |
||||
|
(this.props.topicData !== null ? "" : " grey-text")}> |
||||
|
{this.props.topicData !== null |
||||
|
?this.props.topicData.value[2] |
||||
|
:"Username" |
||||
|
} |
||||
|
</p> |
||||
|
<p className={"no-margin" + |
||||
|
(this.props.topicData !== null ? "" : " grey-text")}> |
||||
|
{"Number of replies: " + (this.props.topicData !== null |
||||
|
?this.props.topicData.value[4].length |
||||
|
:"") |
||||
|
} |
||||
|
</p> |
||||
|
<p className="topic-date grey-text"> |
||||
|
{this.props.topicData !== null && |
||||
|
<TimeAgo date={epochTimeConverter(this.props.topicData.value[3])}/> |
||||
|
} |
||||
|
</p> |
||||
|
</div> |
||||
|
</Card.Content> |
||||
|
</Card> |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
componentDidUpdate(){ |
||||
|
if (this.props.topicData !== null && |
||||
|
this.topicSubjectFetchStatus === "pending" && |
||||
|
this.props.orbitDB.ipfsInitialized && |
||||
|
this.props.orbitDB.orbitdb) { |
||||
|
this.fetchSubject(this.props.topicID); |
||||
|
} |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
user: state.user, |
||||
|
orbitDB: state.orbit |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default withRouter(connect(mapStateToProps)(Topic)); |
@ -0,0 +1,60 @@ |
|||||
|
import React, { Component } from 'react'; |
||||
|
import { connect } from 'react-redux'; |
||||
|
import { drizzle } from '../index'; |
||||
|
|
||||
|
import Topic from './Topic'; |
||||
|
|
||||
|
const contract = "Forum"; |
||||
|
const getTopicMethod = "getTopic"; |
||||
|
|
||||
|
class TopicList extends Component { |
||||
|
constructor(props) { |
||||
|
super(props); |
||||
|
|
||||
|
this.dataKeys = []; |
||||
|
|
||||
|
this.state = { |
||||
|
topicsLoading: true |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
componentDidUpdate(){ |
||||
|
if (this.state.topicsLoading && this.props.drizzleStatus['initialized']){ |
||||
|
var topicsLoading = false; |
||||
|
|
||||
|
this.props.topicIDs.forEach( topicID => { |
||||
|
if (!this.dataKeys[topicID]) { |
||||
|
this.dataKeys[topicID] = drizzle.contracts[contract].methods[getTopicMethod].cacheCall(topicID); |
||||
|
topicsLoading = true; |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
this.setState({ topicsLoading: topicsLoading }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
const topics = this.props.topicIDs.map((topicID) => { |
||||
|
return (<Topic |
||||
|
topicData={(this.dataKeys[topicID] && this.props.contracts[contract][getTopicMethod][this.dataKeys[topicID]]) |
||||
|
? this.props.contracts[contract][getTopicMethod][this.dataKeys[topicID]] |
||||
|
: null} |
||||
|
key={topicID} />) |
||||
|
}); |
||||
|
|
||||
|
return ( |
||||
|
<div className="topics-list"> |
||||
|
{topics.slice(0).reverse()} |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
contracts: state.contracts, |
||||
|
drizzleStatus: state.drizzleStatus |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default connect(mapStateToProps)(TopicList); |
@ -0,0 +1,108 @@ |
|||||
|
import React, { Component } from 'react'; |
||||
|
import { connect } from 'react-redux'; |
||||
|
import { drizzle } from '../index'; |
||||
|
import { withRouter } from 'react-router' |
||||
|
|
||||
|
import { Header } from 'semantic-ui-react'; |
||||
|
|
||||
|
import TopicList from '../components/TopicList'; |
||||
|
import FloatingButton from '../components/FloatingButton'; |
||||
|
|
||||
|
/*import { showProgressBar, hideProgressBar } from '../redux/actions/userInterfaceActions';*/ |
||||
|
|
||||
|
const contract = "Forum"; |
||||
|
const getNumberOfTopicsMethod = "getNumberOfTopics"; |
||||
|
|
||||
|
class BoardContainer extends Component { |
||||
|
constructor(props) { |
||||
|
super(props); |
||||
|
|
||||
|
/*this.props.store.dispatch(showProgressBar());*/ |
||||
|
|
||||
|
this.handleCreateTopicClick = this.handleCreateTopicClick.bind(this); |
||||
|
|
||||
|
this.state = { |
||||
|
pageLoading: true, |
||||
|
pageLoaded: false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
handleCreateTopicClick() { |
||||
|
this.props.history.push("/startTopic"); |
||||
|
} |
||||
|
|
||||
|
componentDidUpdate(){ |
||||
|
if (this.state.pageLoading && !this.state.pageLoaded && this.props.drizzleStatus['initialized']){ |
||||
|
this.dataKey = drizzle.contracts[contract].methods[getNumberOfTopicsMethod].cacheCall(); |
||||
|
this.setState({ pageLoading: false }); |
||||
|
} |
||||
|
if (!this.state.pageLoaded && this.dataKey && |
||||
|
this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey]){ |
||||
|
/*this.props.store.dispatch(hideProgressBar());*/ |
||||
|
this.setState({ pageLoaded: true }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
var boardContents; |
||||
|
if (this.dataKey && this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey]){ |
||||
|
var numberOfTopics = this.props.contracts[contract][getNumberOfTopicsMethod][this.dataKey].value; |
||||
|
|
||||
|
if (numberOfTopics !== '0'){ |
||||
|
this.topicIDs = []; |
||||
|
for (var i = 0; i < numberOfTopics; i++) { |
||||
|
this.topicIDs.push(i); |
||||
|
} |
||||
|
boardContents = ([ |
||||
|
<TopicList topicIDs={this.topicIDs} key="topicList"/>, |
||||
|
<div className="bottom-overlay-pad" key="pad"></div>, |
||||
|
this.props.hasSignedUp && |
||||
|
<FloatingButton onClick={this.handleCreateTopicClick} |
||||
|
key="createTopicButton"/> |
||||
|
]); |
||||
|
} else { |
||||
|
if (!this.props.hasSignedUp){ |
||||
|
boardContents = ( |
||||
|
<div className="vertical-center-in-parent"> |
||||
|
<Header color='teal' textAlign='center' as='h2'> |
||||
|
There are no topics yet! |
||||
|
</Header> |
||||
|
<Header color='teal' textAlign='center' as='h4'> |
||||
|
Sign up to be the first to post. |
||||
|
</Header> |
||||
|
</div> |
||||
|
); |
||||
|
} else { |
||||
|
boardContents = ( |
||||
|
<div className="vertical-center-in-parent"> |
||||
|
<Header color='teal' textAlign='center' as='h2'> |
||||
|
There are no topics yet! |
||||
|
</Header> |
||||
|
<Header color='teal' textAlign='center' as='h4'> |
||||
|
Click the add button at the bottom of the page to be the first to post. |
||||
|
</Header> |
||||
|
<FloatingButton onClick={this.handleCreateTopicClick} |
||||
|
key="createTopicButton"/> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<div className="fill"> |
||||
|
{boardContents} |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
contracts: state.contracts, |
||||
|
drizzleStatus: state.drizzleStatus, |
||||
|
hasSignedUp: state.user.hasSignedUp |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default withRouter(connect(mapStateToProps)(BoardContainer)); |
@ -1,23 +1,26 @@ |
|||||
import React, { Component } from 'react'; |
import React, { Component } from 'react'; |
||||
import { connect } from 'react-redux'; |
|
||||
|
import BoardContainer from './BoardContainer'; |
||||
|
|
||||
class HomeContainer extends Component { |
class HomeContainer extends Component { |
||||
render() { |
render() { |
||||
return (<div className="App"> |
//We can add a modal to tell the user to sign up
|
||||
<div className="section"> |
|
||||
<h1>Active Account</h1> |
/*var modal = this.props.user.hasSignedUp && ( |
||||
{this.props.accounts[0]} |
<Modal dimmer='blurring' open={this.state.open}> |
||||
</div> |
<Modal.Header>Select a Photo</Modal.Header> |
||||
</div>); |
<Modal.Content image> |
||||
|
<Image wrapped size='medium' src='/assets/images/avatar/large/rachel.png' /> |
||||
|
<Modal.Description> |
||||
|
<Header>Default Profile Image</Header> |
||||
|
<p>We've found the following gravatar image associated with your e-mail address.</p> |
||||
|
<p>Is it okay to use this photo?</p> |
||||
|
</Modal.Description> |
||||
|
</Modal.Content> |
||||
|
</Modal>);*/ |
||||
|
|
||||
|
return (<BoardContainer/>); |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
const mapStateToProps = state => { |
export default HomeContainer; |
||||
return { |
|
||||
accounts: state.accounts, |
|
||||
Forum: state.contracts.Forum, |
|
||||
drizzleStatus: state.drizzleStatus |
|
||||
}; |
|
||||
}; |
|
||||
|
|
||||
export default connect(mapStateToProps)(HomeContainer); |
|
||||
|
@ -0,0 +1,132 @@ |
|||||
|
import React, { Component } from 'react'; |
||||
|
import { connect } from 'react-redux'; |
||||
|
|
||||
|
import { Form, TextArea, Button, Icon } from 'semantic-ui-react' |
||||
|
import NewTopicPreview from '../components/NewTopicPreview' |
||||
|
|
||||
|
import { createTopic } from '../redux/actions/transactionsActions'; |
||||
|
|
||||
|
class StartTopicContainer 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.state = { |
||||
|
topicSubjectInput: '', |
||||
|
topicMessageInput: '', |
||||
|
topicSubjectInputEmptySubmit: false, |
||||
|
topicMessageInputEmptySubmit: false, |
||||
|
previewEnabled: false, |
||||
|
previewDate: "" |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
async validateAndPost() { |
||||
|
if (this.state.topicSubjectInput === '' || this.state.topicMessageInput === ''){ |
||||
|
this.setState({ |
||||
|
topicSubjectInputEmptySubmit: this.state.topicSubjectInput === '', |
||||
|
topicMessageInputEmptySubmit: this.state.topicMessageInput === '' |
||||
|
}); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
this.props.dispatch( |
||||
|
createTopic( |
||||
|
{ |
||||
|
topicSubject: this.state.topicSubjectInput, |
||||
|
topicMessage: this.state.topicMessageInput |
||||
|
} |
||||
|
) |
||||
|
); |
||||
|
this.props.history.push("/home"); |
||||
|
} |
||||
|
|
||||
|
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() { |
||||
|
if (!this.props.user.hasSignedUp) { |
||||
|
this.props.history.push("/signup"); |
||||
|
return(null); |
||||
|
} |
||||
|
|
||||
|
var previewEditText = this.state.previewEnabled ? "Edit" : "Preview"; |
||||
|
return ( |
||||
|
<div> |
||||
|
{this.state.previewEnabled && |
||||
|
<NewTopicPreview |
||||
|
date={this.state.previewDate} |
||||
|
subject={this.state.topicSubjectInput} |
||||
|
content={this.state.topicMessageInput} |
||||
|
/> |
||||
|
} |
||||
|
<Form> |
||||
|
{!this.state.previewEnabled && |
||||
|
[<Form.Field key={"topicSubjectInput"}> |
||||
|
<Form.Input name={"topicSubjectInput"} |
||||
|
error={this.state.topicSubjectInputEmptySubmit} |
||||
|
type="text" |
||||
|
value={this.state.topicSubjectInput} |
||||
|
placeholder="Subject" |
||||
|
id="topicSubjectInput" |
||||
|
onChange={this.handleInputChange} /> |
||||
|
</Form.Field>, |
||||
|
<TextArea key={"topicMessageInput"} |
||||
|
name={"topicMessageInput"} |
||||
|
className={this.state.topicMessageInputEmptySubmit ? "form-textarea-required" : ""} |
||||
|
value={this.state.topicMessageInput} |
||||
|
placeholder="Post" |
||||
|
id="topicMessageInput" |
||||
|
rows={5} |
||||
|
autoHeight |
||||
|
onChange={this.handleInputChange} />] |
||||
|
} |
||||
|
<br/><br/> |
||||
|
<Button.Group> |
||||
|
<Button animated key="submit" type="button" color='teal' |
||||
|
onClick={this.validateAndPost}> |
||||
|
<Button.Content visible>Post</Button.Content> |
||||
|
<Button.Content hidden> |
||||
|
<Icon name='send' /> |
||||
|
</Button.Content> |
||||
|
</Button> |
||||
|
<Button type="button" color='yellow' |
||||
|
onClick={this.handlePreviewToggle}> |
||||
|
{previewEditText} |
||||
|
</Button> |
||||
|
</Button.Group> |
||||
|
</Form> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
orbitDB: state.orbitDB, |
||||
|
user: state.user |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default connect(mapStateToProps)(StartTopicContainer); |
@ -0,0 +1,12 @@ |
|||||
|
const epochTimeConverter = (timestamp) => { |
||||
|
var timestampDate = new Date(0); |
||||
|
timestampDate.setUTCSeconds(timestamp); |
||||
|
return ((timestampDate.getMonth() + 1) + " " |
||||
|
+ timestampDate.getDate() + ", " |
||||
|
+ timestampDate.getFullYear() + ", " |
||||
|
+ timestampDate.getHours() + ":" |
||||
|
+ timestampDate.getMinutes() + ":" |
||||
|
+ timestampDate.getSeconds()) |
||||
|
} |
||||
|
|
||||
|
export default epochTimeConverter; |
@ -0,0 +1,58 @@ |
|||||
|
//Action creators
|
||||
|
import uuid from 'uuid/v1'; |
||||
|
|
||||
|
export const INIT_TRANSACTION = 'INIT_TRANSACTION'; |
||||
|
export const UPDATE_TRANSACTION = 'UPDATE_TRANSACTION'; |
||||
|
|
||||
|
export function updateUsername(newUsername, callback){ |
||||
|
return { |
||||
|
type: INIT_TRANSACTION, |
||||
|
transactionDescriptor: |
||||
|
{ |
||||
|
contract: 'Forum', |
||||
|
method: 'updateUsername', |
||||
|
params: [newUsername], |
||||
|
event: 'UsernameUpdated' |
||||
|
}, |
||||
|
uid: uuid(), |
||||
|
callback: callback |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function createTopic(userInputs){ |
||||
|
return { |
||||
|
type: INIT_TRANSACTION, |
||||
|
transactionDescriptor: |
||||
|
{ |
||||
|
contract: 'Forum', |
||||
|
method: 'createTopic', |
||||
|
params: [], |
||||
|
event: 'TopicCreated' |
||||
|
}, |
||||
|
uid: uuid(), |
||||
|
userInputs: userInputs |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function createPost(topicID, userInputs){ |
||||
|
return { |
||||
|
type: INIT_TRANSACTION, |
||||
|
transactionDescriptor: |
||||
|
{ |
||||
|
contract: 'Forum', |
||||
|
method: 'createPost', |
||||
|
params: [topicID], |
||||
|
event: 'PostCreated' |
||||
|
}, |
||||
|
uid: uuid(), |
||||
|
userInputs: userInputs |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
export function updateTransaction(transactionIndex, updateDescriptor){ |
||||
|
return { |
||||
|
type: UPDATE_TRANSACTION, |
||||
|
index: transactionIndex, |
||||
|
transactionUpdates: updateDescriptor |
||||
|
}; |
||||
|
} |
@ -0,0 +1,39 @@ |
|||||
|
import { INIT_TRANSACTION, UPDATE_TRANSACTION } from '../actions/transactionsMonitorActions'; |
||||
|
|
||||
|
const initialState = { |
||||
|
transactions: Object.create(null) |
||||
|
}; |
||||
|
|
||||
|
const transactionsReducer = (state = initialState, action) => { |
||||
|
switch (action.type) { |
||||
|
case INIT_TRANSACTION: |
||||
|
let transactionsShallowCopy = state.transactions.slice(); |
||||
|
transactionsShallowCopy.push({ |
||||
|
status: 'initialized', |
||||
|
contract: action.transactionDescriptor.contract, |
||||
|
method: action.transactionDescriptor.method, |
||||
|
params: action.transactionDescriptor.params, |
||||
|
event: action.transactionDescriptor.event, |
||||
|
returnData: null, |
||||
|
userInputs: action.userInputs |
||||
|
}); |
||||
|
return { |
||||
|
transactions: transactionsShallowCopy |
||||
|
}; |
||||
|
case UPDATE_TRANSACTION: |
||||
|
return { transactions: state.transactions.map( (transaction, index) => { |
||||
|
if (index !== action.index){ |
||||
|
return transaction; |
||||
|
} |
||||
|
|
||||
|
return { |
||||
|
...transaction, |
||||
|
...action.transactionUpdates |
||||
|
} |
||||
|
})}; |
||||
|
default: |
||||
|
return state; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default transactionsReducer; |
@ -0,0 +1,36 @@ |
|||||
|
import {call, put, select, take, takeEvery} from 'redux-saga/effects' |
||||
|
import { contract, getCurrentAccount } from './drizzleUtilsSaga'; |
||||
|
|
||||
|
import { drizzle } from '../../index' |
||||
|
|
||||
|
let transactionsHistory = Object.create(null); |
||||
|
|
||||
|
function* initTransaction(action) { |
||||
|
transactionsHistory[action.uid] = action; |
||||
|
transactionsHistory[action.uid].dataKey = drizzle.contracts[action.transactionDescriptor.contract] |
||||
|
.methods[action.transactionDescriptor['method']] |
||||
|
.cacheSend(...[action.transactionDescriptor.params]); |
||||
|
|
||||
|
transactionsHistory[action.uid].state = 'initialized'; |
||||
|
} |
||||
|
|
||||
|
function* completeWithOrbitInteractions(action) { |
||||
|
const orbit = yield select((state) => state.orbit); |
||||
|
|
||||
|
yield call(orbit.topicsDB.put, action.receipt.events['TopicCreated'].returnValues.topicID, { |
||||
|
subject: 'tada' |
||||
|
}); |
||||
|
|
||||
|
yield call(orbit.postsDB.put, action.receipt.events['TopicCreated'].returnValues.postID, { |
||||
|
subject: 'tada', |
||||
|
content: 'it worked!' |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
function* transactionsSaga() { |
||||
|
yield take("DRIZZLE_UTILS_SAGA_INITIALIZED"); |
||||
|
yield takeEvery("INIT_TRANSACTION", initTransaction); |
||||
|
yield takeEvery("TX_SUCCESSFUL", completeWithOrbitInteractions); |
||||
|
} |
||||
|
|
||||
|
export default transactionsSaga; |
Loading…
Reference in new issue