@ -1,19 +0,0 @@ |
|||||
import React, { Component } from 'react' |
|
||||
|
|
||||
// Styles
|
|
||||
import './css/oswald.css' |
|
||||
import './css/open-sans.css' |
|
||||
import './css/pure-min.css' |
|
||||
import './css/App.css' |
|
||||
|
|
||||
class App extends Component { |
|
||||
render() { |
|
||||
return ( |
|
||||
<div className="App"> |
|
||||
{this.props.children} |
|
||||
</div> |
|
||||
); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default App |
|
@ -1,8 +0,0 @@ |
|||||
import React from 'react'; |
|
||||
import ReactDOM from 'react-dom'; |
|
||||
import App from './App'; |
|
||||
|
|
||||
it('renders without crashing', () => { |
|
||||
const div = document.createElement('div'); |
|
||||
ReactDOM.render(<App />, div); |
|
||||
}); |
|
@ -0,0 +1,339 @@ |
|||||
|
/* PAGE */ |
||||
|
|
||||
|
html, body { |
||||
|
margin: 0; |
||||
|
display: block; |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
body, |
||||
|
.pure-g [class*=pure-u] { |
||||
|
font-family: 'Open Sans', sans-serif; |
||||
|
} |
||||
|
|
||||
|
.App { |
||||
|
width: 100%; |
||||
|
margin: 0px; |
||||
|
display: flex; |
||||
|
flex-flow: column nowrap; |
||||
|
align-items: flex-start; |
||||
|
} |
||||
|
|
||||
|
.view-container { |
||||
|
width: calc(100% - 24px); |
||||
|
margin: 10px 12px; |
||||
|
} |
||||
|
|
||||
|
.container { |
||||
|
box-sizing: border-box; |
||||
|
width: calc(100% - 60px); |
||||
|
max-width: 600px; |
||||
|
padding: 45px 20px; |
||||
|
margin: 0 auto; |
||||
|
} |
||||
|
|
||||
|
/* LOADING SCREEN */ |
||||
|
|
||||
|
.loading-screen { |
||||
|
opacity: 1; |
||||
|
visibility: visible; |
||||
|
transition: all .25s ease-in-out; |
||||
|
} |
||||
|
|
||||
|
.loading-screen.loaded { |
||||
|
opacity: 0; |
||||
|
visibility: hidden; |
||||
|
} |
||||
|
|
||||
|
/* HEADER BAR */ |
||||
|
|
||||
|
.header-bar { |
||||
|
max-height: 100px; |
||||
|
max-width: 100%; |
||||
|
padding: 7px 0px; |
||||
|
background: #0c1a2b; |
||||
|
color: white; |
||||
|
align-self: center; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
.logo { |
||||
|
height: 100%; |
||||
|
} |
||||
|
|
||||
|
.header-bar-text { |
||||
|
height: 100%; |
||||
|
margin-left: 8px; |
||||
|
} |
||||
|
|
||||
|
.header-bar-text div { |
||||
|
height: 100%; |
||||
|
display: table; |
||||
|
} |
||||
|
|
||||
|
.header-bar-text div>div { |
||||
|
display: table-cell; |
||||
|
vertical-align: middle; |
||||
|
} |
||||
|
|
||||
|
/* NAVBAR */ |
||||
|
|
||||
|
.navbar { |
||||
|
max-width: 100%; |
||||
|
margin: 0px; |
||||
|
background: #0c1a2b; |
||||
|
font-family: 'Oswald', 'Arial Narrow', sans-serif; |
||||
|
} |
||||
|
|
||||
|
.navbar ul { |
||||
|
list-style-type: none; |
||||
|
margin: 0; |
||||
|
padding: 0; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
|
||||
|
.navbar li { |
||||
|
float: left; |
||||
|
} |
||||
|
|
||||
|
.navbar li a { |
||||
|
display: block; |
||||
|
color: white; |
||||
|
text-align: center; |
||||
|
padding: 14px 16px; |
||||
|
text-decoration: none; |
||||
|
} |
||||
|
|
||||
|
.navbar a:active, |
||||
|
.navbar a:focus, |
||||
|
.navbar a:hover { |
||||
|
background: #233e5e; |
||||
|
} |
||||
|
|
||||
|
/* TOPICS */ |
||||
|
|
||||
|
.topic { |
||||
|
width: calc(100% - 14px); |
||||
|
background-color: #FFFFFF; |
||||
|
margin: 12px 0px; |
||||
|
padding: 7px; |
||||
|
} |
||||
|
|
||||
|
.topic-subject { |
||||
|
margin: 0px 0px 5px; |
||||
|
} |
||||
|
|
||||
|
.topic-meta { |
||||
|
margin: 5px 0px 0px; |
||||
|
} |
||||
|
|
||||
|
.topic-date { |
||||
|
margin-top: 2px; |
||||
|
margin-bottom: 0px; |
||||
|
color: #333333; |
||||
|
font-size: 0.82vw; |
||||
|
text-align: right; |
||||
|
} |
||||
|
|
||||
|
/* START TOPIC */ |
||||
|
|
||||
|
.start-topic-back-button { |
||||
|
background-color: transparent; |
||||
|
margin-left: 7px; |
||||
|
margin-bottom: 7px; |
||||
|
color: #0c1a2b; |
||||
|
} |
||||
|
|
||||
|
.start-topic-back-button .fa-arrow-left{ |
||||
|
filter:drop-shadow(1px 1px 2px #0c1a2b); |
||||
|
} |
||||
|
|
||||
|
.start-topic-back-button .fa-arrow-left:active{ |
||||
|
filter:drop-shadow(0px 1px 1px #0A1624); |
||||
|
color: #0A1624; |
||||
|
transform: translateY(3px); |
||||
|
} |
||||
|
|
||||
|
.start-topic-back-button:focus { |
||||
|
outline:none; |
||||
|
} |
||||
|
|
||||
|
.topic-form input[type=text], textarea { |
||||
|
width: 100%; |
||||
|
padding: 12px 20px; |
||||
|
margin: 8px 0; |
||||
|
display: inline-block; |
||||
|
border: 1px solid #ccc; |
||||
|
border-radius: 4px; |
||||
|
resize: vertical; |
||||
|
box-sizing: border-box; |
||||
|
} |
||||
|
|
||||
|
.topic-form textarea { |
||||
|
height: 200px; |
||||
|
} |
||||
|
|
||||
|
.markdownPreview { |
||||
|
padding: 0px 20px; |
||||
|
} |
||||
|
|
||||
|
/* FLOATING BUTTONS */ |
||||
|
|
||||
|
.floating-button{ |
||||
|
position:fixed; |
||||
|
width:60px; |
||||
|
height:60px; |
||||
|
bottom:40px; |
||||
|
right:40px; |
||||
|
background-color:#0C9; |
||||
|
color:#FFF; |
||||
|
border-radius:50px; |
||||
|
text-align:center; |
||||
|
box-shadow: 2px 2px 3px #999; |
||||
|
} |
||||
|
|
||||
|
.floating-button:focus { |
||||
|
outline:none; |
||||
|
} |
||||
|
|
||||
|
.floating-button:active { |
||||
|
background-color: #00B386; |
||||
|
box-shadow: 0px 1px 3px #333; |
||||
|
transform: translateY(4px); |
||||
|
} |
||||
|
|
||||
|
.floating-button svg { |
||||
|
min-height: 100%; |
||||
|
text-align: center; |
||||
|
vertical-align: middle; |
||||
|
} |
||||
|
|
||||
|
/* POSTS */ |
||||
|
|
||||
|
.post { |
||||
|
width: calc(100% - 14px); |
||||
|
background-color: #FFFFFF; |
||||
|
margin: 12px 0px; |
||||
|
padding: 7px; |
||||
|
} |
||||
|
|
||||
|
.post-header { |
||||
|
margin-bottom: 2px; |
||||
|
} |
||||
|
|
||||
|
.user-avatar { |
||||
|
max-height: 40px; |
||||
|
margin-right: 7px; |
||||
|
} |
||||
|
|
||||
|
.post-info { |
||||
|
width: 100%; |
||||
|
margin-top: 2px; |
||||
|
margin-bottom: 0px; |
||||
|
display: flex; |
||||
|
flex-flow: row nowrap; |
||||
|
justify-content: space-between; |
||||
|
color: #333333; |
||||
|
font-size: 0.82vw; |
||||
|
} |
||||
|
|
||||
|
.post-content { |
||||
|
margin: 0px; |
||||
|
} |
||||
|
|
||||
|
.post-meta { |
||||
|
margin-top: 2px; |
||||
|
} |
||||
|
|
||||
|
.user-column { |
||||
|
margin: 0px 10px; |
||||
|
order: 2; |
||||
|
} |
||||
|
|
||||
|
.user-info { |
||||
|
display: inline-block; |
||||
|
background-color: #FFFFFF; |
||||
|
margin: 12px auto; |
||||
|
padding: 7px; |
||||
|
} |
||||
|
|
||||
|
/* FORMS */ |
||||
|
|
||||
|
.pure-form { |
||||
|
display: inline-block; |
||||
|
} |
||||
|
|
||||
|
.pure-form input[type="text"]:focus { |
||||
|
border-color: #0c1a2b; |
||||
|
} |
||||
|
|
||||
|
/* BUTTONS */ |
||||
|
|
||||
|
.pure-button-primary { |
||||
|
background-color: #0c1a2b; |
||||
|
} |
||||
|
|
||||
|
.pure-button-primary:hover { |
||||
|
background-color: #233e5e; |
||||
|
} |
||||
|
|
||||
|
.pure-button-primary:active { |
||||
|
background-color: #0c1a2b; |
||||
|
} |
||||
|
|
||||
|
/* ALERTS */ |
||||
|
|
||||
|
.alert { |
||||
|
padding: .5rem; |
||||
|
color: darkgreen; |
||||
|
background: darkseagreen; |
||||
|
border: 1px solid darkgreen; |
||||
|
} |
||||
|
|
||||
|
/* MISC */ |
||||
|
|
||||
|
.inline { |
||||
|
display: inline-block; |
||||
|
} |
||||
|
|
||||
|
.no-margin { |
||||
|
margin: 0px; |
||||
|
} |
||||
|
|
||||
|
.margin-left-small { |
||||
|
margin-left: 7px; |
||||
|
} |
||||
|
|
||||
|
.card { |
||||
|
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16),0 2px 10px 0 rgba(0,0,0,0.12); |
||||
|
} |
||||
|
|
||||
|
.card:hover { |
||||
|
box-shadow:0 4px 10px 0 rgba(0,0,0,0.2),0 4px 20px 0 rgba(0,0,0,0.19) |
||||
|
} |
||||
|
|
||||
|
hr { |
||||
|
color: #0c1a2b; |
||||
|
margin: 0px; |
||||
|
} |
||||
|
|
||||
|
h1, h2, h3 { |
||||
|
font-family: 'Oswald', 'Arial Narrow', sans-serif; |
||||
|
} |
||||
|
|
||||
|
a:focus {outline:none} |
||||
|
|
||||
|
code { |
||||
|
padding: .25rem; |
||||
|
margin: 0 .25rem; |
||||
|
background: #eee; |
||||
|
} |
||||
|
|
||||
|
.center { |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
a { |
||||
|
color:inherit; |
||||
|
text-decoration: none; |
||||
|
} |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB |
@ -0,0 +1,13 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
const FloatingButton = (props) => { |
||||
|
return ( |
||||
|
<div className="pure-u-1-1"> |
||||
|
<p className="no-margin floating-button" data-fa-transform="down-6" onClick={props.onClick}> |
||||
|
<i className="fa fa-plus fa-2x"></i> |
||||
|
</p> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default FloatingButton; |
@ -0,0 +1,19 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
const HeaderBar = (props) => { |
||||
|
return ( |
||||
|
<div className="header-bar"> |
||||
|
<img className="logo" src={require('../resources/logo.png')} alt="logo"/> |
||||
|
<div className="inline header-bar-text"> |
||||
|
<div> |
||||
|
<div> |
||||
|
<h1 className="no-margin">Welcome to Apella</h1> |
||||
|
<p className="no-margin">A decentralized forum</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default HeaderBar; |
@ -0,0 +1,28 @@ |
|||||
|
import React from 'react'; |
||||
|
import { drizzleConnect } from 'drizzle-react'; |
||||
|
import { Link } from 'react-router'; |
||||
|
|
||||
|
const NavBar = (props) => { |
||||
|
return ( |
||||
|
<div className="pure-u-1-1 navbar"> |
||||
|
<ul> |
||||
|
<li> |
||||
|
<Link to="/">Home</Link> |
||||
|
</li> |
||||
|
{props.hasSignedUp && |
||||
|
<li> |
||||
|
<Link to="/profile">Profile</Link> |
||||
|
</li> |
||||
|
} |
||||
|
</ul> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
hasSignedUp: state.user.hasSignedUp |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default drizzleConnect(NavBar, mapStateToProps); |
@ -0,0 +1,91 @@ |
|||||
|
import React, { Component } from 'react'; |
||||
|
import { drizzleConnect } from 'drizzle-react'; |
||||
|
import UserAvatar from 'react-user-avatar'; |
||||
|
import ReactMarkdown from 'react-markdown'; |
||||
|
|
||||
|
class NewPost extends Component { |
||||
|
constructor(props, context) { |
||||
|
super(props); |
||||
|
|
||||
|
this.handleInputChange = this.handleInputChange.bind(this); |
||||
|
this.handlePreviewToggle = this.handlePreviewToggle.bind(this); |
||||
|
this.handleSubmit = this.handleSubmit.bind(this); |
||||
|
|
||||
|
this.state = { |
||||
|
postContent: '', |
||||
|
previewEnabled: false |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
async handleSubmit() { |
||||
|
/*this.stackId = this.contracts[contract].methods[startTopicMethod].cacheSend();*/ |
||||
|
} |
||||
|
|
||||
|
handleInputChange(event) { |
||||
|
this.setState({[event.target.name]: event.target.value}); |
||||
|
} |
||||
|
|
||||
|
handlePreviewToggle(){ |
||||
|
this.setState((prevState, props) => ({ |
||||
|
previewEnabled: !prevState.previewEnabled |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
return ( |
||||
|
<div className="pure-u-1-1 post card"> |
||||
|
<div className="post-header"> |
||||
|
<UserAvatar |
||||
|
size="40" |
||||
|
className="inline user-avatar" |
||||
|
src={this.props.user.avatarUrl} |
||||
|
name={this.props.user.username}/> |
||||
|
<p className="inline no-margin"> |
||||
|
<strong>{this.props.user.username}<br/>Subject: {this.props.subject}</strong> |
||||
|
</p> |
||||
|
<div className="post-info"> |
||||
|
<span></span> |
||||
|
<span>#{this.props.postIndex}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
<hr/> |
||||
|
<div className="post-content"> |
||||
|
<form className="topic-form"> |
||||
|
{this.state.previewEnabled |
||||
|
? <ReactMarkdown source={this.state.postContent} className="markdownPreview" /> |
||||
|
: <textarea key={"postContent"} |
||||
|
name={"postContent"} |
||||
|
value={this.state.postContent} |
||||
|
placeholder="Post" |
||||
|
id="postContent" |
||||
|
onChange={this.handleInputChange} />} |
||||
|
<button key="submit" |
||||
|
className="pure-button pure-button-primary" |
||||
|
type="button" |
||||
|
onClick={this.handleSubmit}> |
||||
|
Post |
||||
|
</button> |
||||
|
<button className="pure-button margin-left-small" |
||||
|
type="button" |
||||
|
onClick={this.handlePreviewToggle}> |
||||
|
{this.state.previewEnabled ? "Edit" : "Preview"} |
||||
|
</button> |
||||
|
<button className="pure-button margin-left-small" |
||||
|
type="button" |
||||
|
onClick={this.props.onCancelClick}> |
||||
|
Cancel |
||||
|
</button> |
||||
|
</form> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
user: state.user |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default drizzleConnect(NewPost, mapStateToProps); |
@ -0,0 +1,11 @@ |
|||||
|
import React from 'react'; |
||||
|
|
||||
|
const NotFoundView = (props) => { |
||||
|
return ( |
||||
|
<div className="pure-u-1-1 center"> |
||||
|
<img src={require('../resources/PageNotFound.jpg')} alt="Page not found!"/> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default NotFoundView; |
@ -0,0 +1,37 @@ |
|||||
|
import React from 'react'; |
||||
|
import UserAvatar from 'react-user-avatar'; |
||||
|
import TimeAgo from 'react-timeago'; |
||||
|
import ReactMarkdown from 'react-markdown'; |
||||
|
|
||||
|
const Post = (props) => { |
||||
|
const username = props.username && [props.username, <br key={props.id}/>]; |
||||
|
|
||||
|
return ( |
||||
|
<div className="pure-u-1-1 post card"> |
||||
|
<div className="post-header"> |
||||
|
{props.avatarUrl && <UserAvatar |
||||
|
size="40" |
||||
|
className="inline user-avatar" |
||||
|
src={props.avatarUrl} |
||||
|
name={props.username}/>} |
||||
|
<p className="inline no-margin"> |
||||
|
<strong>{username}Subject: {props.subject}</strong> |
||||
|
</p> |
||||
|
<div className="post-info"> |
||||
|
<span>Posted <TimeAgo date={props.date}/></span> |
||||
|
{props.postIndex && <span>#{props.postIndex}</span>} |
||||
|
</div> |
||||
|
</div> |
||||
|
<hr/> |
||||
|
<div className="post-content"> |
||||
|
<ReactMarkdown source={props.postContent} /> |
||||
|
</div> |
||||
|
<hr/> |
||||
|
<div className="post-meta"> |
||||
|
Maybe add buttons for upvote etc here... |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default Post; |
@ -0,0 +1,45 @@ |
|||||
|
import React from 'react'; |
||||
|
import Post from './Post'; |
||||
|
|
||||
|
const posts1 = [ |
||||
|
{avatarUrl: "https://i.makeagif.com/media/4-18-2018/8BLiwJ.gif", |
||||
|
username: "Apostolof", |
||||
|
subject: "Some very important topic of discussion2!", |
||||
|
date: "May 25, 2018, 11:11:11", |
||||
|
postIndex: "1", |
||||
|
postContent: "# We have markdown!!!\n \n**Oh yes we do!!** \n*ITALICS* \n \n```Some code```", |
||||
|
id: 2, |
||||
|
address: 0x083c41ea13af6c2d5aaddf6e73142eb9a7b00183 |
||||
|
}, |
||||
|
{avatarUrl: "", |
||||
|
username: "", |
||||
|
subject: "Some very important topic of discussion!", |
||||
|
date: "May 20, 2018, 10:10:10", |
||||
|
postIndex: "", |
||||
|
postContent: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consequatur, natus ipsum minima.", |
||||
|
id: 1, |
||||
|
address: 0x5fe3062B24033113fbf52b2b75882890D7d8CA54 |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
const PostList = (props) => { |
||||
|
const posts = posts1.map((post) => |
||||
|
<Post avatarUrl={post.avatarUrl} |
||||
|
username={post.username} |
||||
|
subject={post.subject} |
||||
|
date={post.date} |
||||
|
postIndex={post.postIndex} |
||||
|
postContent={post.postContent} |
||||
|
id={post.id} |
||||
|
key={post.id} |
||||
|
address={post.address}/> |
||||
|
); |
||||
|
|
||||
|
return ( |
||||
|
<div className="posts-list"> |
||||
|
{posts} |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default PostList; |
@ -0,0 +1,30 @@ |
|||||
|
import React from 'react'; |
||||
|
import UserAvatar from 'react-user-avatar'; |
||||
|
import UsernameFormContainer from '../containers/UsernameFormContainer'; |
||||
|
|
||||
|
const ProfileInformation = (props) => { |
||||
|
return ( |
||||
|
<div className="pure-u-1-1 user-info card"> |
||||
|
{props.avatarUrl && <UserAvatar |
||||
|
size="40" |
||||
|
className="inline user-avatar" |
||||
|
src={props.avatarUrl} |
||||
|
name={props.username}/>} |
||||
|
<p className="no-margin inline"> |
||||
|
<strong>Username</strong>: {props.username} |
||||
|
</p> |
||||
|
<p className="no-margin"> |
||||
|
<strong>Account address:</strong> {props.address} |
||||
|
</p> |
||||
|
<p className="no-margin"> |
||||
|
<strong>OrbitDB:</strong> {props.orbitAddress} |
||||
|
</p> |
||||
|
<p className="no-margin"> |
||||
|
Number of posts: TODO? |
||||
|
</p> |
||||
|
<UsernameFormContainer/> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default ProfileInformation; |
@ -0,0 +1,126 @@ |
|||||
|
import { drizzleConnect } from 'drizzle-react' |
||||
|
import React, { Component } from 'react' |
||||
|
import PropTypes from 'prop-types' |
||||
|
import Post from './Post' |
||||
|
|
||||
|
const contract = "Forum"; |
||||
|
const startTopicMethod = "createTopic"; |
||||
|
|
||||
|
class StartTopic extends Component { |
||||
|
constructor(props, context) { |
||||
|
super(props); |
||||
|
|
||||
|
this.handleInputChange = this.handleInputChange.bind(this); |
||||
|
this.handlePreviewToggle = this.handlePreviewToggle.bind(this); |
||||
|
this.handleSubmit = this.handleSubmit.bind(this); |
||||
|
|
||||
|
this.drizzle = context.drizzle; |
||||
|
this.drizzleState = this.drizzle.store.getState(); |
||||
|
this.contracts = this.drizzle.contracts; |
||||
|
this.abi = this.contracts[contract].abi; |
||||
|
this.state = { |
||||
|
topicSubjectInput: '', |
||||
|
topicMessageInput: '', |
||||
|
previewEnabled: false, |
||||
|
previewDate: "" |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
async handleSubmit() { |
||||
|
console.log("contracts:"); |
||||
|
console.log(this.contracts); |
||||
|
console.log("DS contracts:"); |
||||
|
console.log(this.drizzleState.contracts); |
||||
|
|
||||
|
this.dataKey = this.drizzleState.contracts[contract].methods[startTopicMethod].cacheCall(); |
||||
|
//TODO get return value and pass it to orbit
|
||||
|
} |
||||
|
|
||||
|
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.dataKey) { |
||||
|
/*console.log(this.drizzleState);*/ |
||||
|
if (this.drizzleState.contracts[contract]) { |
||||
|
console.log(this.drizzleState.contracts[contract].storedData[this.dataKey].value); |
||||
|
} |
||||
|
} |
||||
|
return( |
||||
|
<div> |
||||
|
<div className="pure-u-1-1 start-topic-back-button"> |
||||
|
<p className="no-margin" onClick={this.props.onClick}> |
||||
|
<i className="fas fa-arrow-left fa-3x"></i> |
||||
|
</p> |
||||
|
</div> |
||||
|
{this.state.previewEnabled && |
||||
|
<Post avatarUrl={this.props.user.avatarUrl} |
||||
|
username={this.props.user.username} |
||||
|
subject={this.state.topicSubjectInput} |
||||
|
date={this.state.previewDate} |
||||
|
postContent={this.state.topicMessageInput} |
||||
|
id={0}/>} |
||||
|
<form className="topic-form"> |
||||
|
{!this.state.previewEnabled && |
||||
|
[ |
||||
|
<input key={"topicSubjectInput"} |
||||
|
name={"topicSubjectInput"} |
||||
|
type="text" |
||||
|
value={this.state.topicSubjectInput} |
||||
|
placeholder="Subject" |
||||
|
id="topicSubjectInput" |
||||
|
onChange={this.handleInputChange} />, |
||||
|
<textarea key={"topicMessageInput"} |
||||
|
name={"topicMessageInput"} |
||||
|
value={this.state.topicMessageInput} |
||||
|
placeholder="Post" |
||||
|
id="topicMessageInput" |
||||
|
onChange={this.handleInputChange} /> |
||||
|
]} |
||||
|
<button key="submit" |
||||
|
className="pure-button" |
||||
|
type="button" |
||||
|
onClick={this.handleSubmit}> |
||||
|
Post |
||||
|
</button> |
||||
|
<button className="pure-button margin-left-small" |
||||
|
type="button" |
||||
|
onClick={this.handlePreviewToggle}> |
||||
|
{this.state.previewEnabled ? "Edit" : "Preview"} |
||||
|
</button> |
||||
|
</form> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
StartTopic.contextTypes = { |
||||
|
drizzle: PropTypes.object |
||||
|
}; |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
contracts: state.contracts, |
||||
|
user: state.user |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
export default drizzleConnect(StartTopic, mapStateToProps); |
@ -0,0 +1,18 @@ |
|||||
|
import React from 'react'; |
||||
|
import TimeAgo from 'react-timeago'; |
||||
|
|
||||
|
const Topic = (props) => { |
||||
|
return ( |
||||
|
<div className="topic card"> |
||||
|
<p className="topic-subject"><strong>{props.topicSubject}</strong></p> |
||||
|
<hr/> |
||||
|
<div className="topic-meta"> |
||||
|
<p className="no-margin">{props.topicStarter}</p> |
||||
|
<p className="no-margin">Number of replies: {props.numberOfReplies}</p> |
||||
|
<p className="topic-date">Started <TimeAgo date={props.date}/></p> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default Topic; |
@ -0,0 +1,50 @@ |
|||||
|
import React from 'react'; |
||||
|
import Topic from './Topic'; |
||||
|
import { Link } from 'react-router'; |
||||
|
|
||||
|
const topics1 = [ |
||||
|
{topicSubject: 'This is a topic about something 1', |
||||
|
topicStarter: 'username1', |
||||
|
numberOfReplies: 12, |
||||
|
date: 'May 20, 2018, 10:10:10', |
||||
|
id: 1, |
||||
|
address: 0x5fe3062B24033113fbf52b2b75882890D7d8CA54 |
||||
|
}, |
||||
|
{topicSubject: 'This is a topic about something 2', |
||||
|
topicStarter: 'username2', |
||||
|
numberOfReplies: 41, |
||||
|
date: 'May 20, 2018, 10:10:10', |
||||
|
id: 2, |
||||
|
address: 0x083c41ea13af6c2d5aaddf6e73142eb9a7b00183 |
||||
|
}, |
||||
|
{topicSubject: 'This is a topic about something 3', |
||||
|
topicStarter: 'username3', |
||||
|
numberOfReplies: 73, |
||||
|
date: 'May 20, 2018, 10:10:10', |
||||
|
id: 3, |
||||
|
address: 0x26d1ec50b4e62c1d1a40d16e7cacc6a6580757d5 |
||||
|
} |
||||
|
]; |
||||
|
|
||||
|
const TopicList = (props) => { |
||||
|
const topics = topics1.map((topic) => |
||||
|
<Link to={"/topic/" + topic.id + "/" + topic.topicSubject} |
||||
|
key={topic.id}> |
||||
|
<Topic topicSubject={topic.topicSubject} |
||||
|
topicStarter={topic.topicStarter} |
||||
|
numberOfReplies={topic.numberOfReplies} |
||||
|
date={topic.date} |
||||
|
id={topic.id} |
||||
|
key={topic.id} |
||||
|
address={topic.address}/> |
||||
|
</Link> |
||||
|
); |
||||
|
|
||||
|
return ( |
||||
|
<div className="topics-list"> |
||||
|
{topics} |
||||
|
</div> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
export default TopicList; |
@ -0,0 +1,50 @@ |
|||||
|
import { drizzleConnect } from 'drizzle-react'; |
||||
|
import React, { Component } from 'react'; |
||||
|
import TopicList from '../components/TopicList'; |
||||
|
import FloatingButton from '../components/FloatingButton'; |
||||
|
import StartTopic from '../components/StartTopic'; |
||||
|
|
||||
|
class Board extends Component { |
||||
|
constructor(props) { |
||||
|
super(props); |
||||
|
this.state = { |
||||
|
startingNewTopic: false |
||||
|
}; |
||||
|
|
||||
|
this.handleClick = this.handleClick.bind(this); |
||||
|
} |
||||
|
|
||||
|
handleClick(event) { |
||||
|
event.preventDefault(); |
||||
|
this.setState(prevState => ({ |
||||
|
startingNewTopic: !prevState.startingNewTopic |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
return ( |
||||
|
this.state.startingNewTopic |
||||
|
?(<div> |
||||
|
<StartTopic onClick={this.handleClick}/> |
||||
|
</div>) |
||||
|
:(<div style={{marginBottom: '100px'}}> |
||||
|
<TopicList/> |
||||
|
<FloatingButton onClick={this.handleClick}/> |
||||
|
</div>) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
accounts: state.accounts, |
||||
|
Forum: state.contracts.Forum, |
||||
|
user: state.user, |
||||
|
orbitDB: state.orbitDB, |
||||
|
drizzleStatus: state.drizzleStatus |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const BoardContainer = drizzleConnect(Board, mapStateToProps); |
||||
|
|
||||
|
export default BoardContainer; |
@ -0,0 +1,29 @@ |
|||||
|
import { drizzleConnect } from 'drizzle-react'; |
||||
|
import React, { Component } from 'react'; |
||||
|
import SignUpContainer from './SignUpContainer'; |
||||
|
import BoardContainer from './BoardContainer'; |
||||
|
|
||||
|
class Home extends Component { |
||||
|
render() { |
||||
|
//This must change to routes and redirects
|
||||
|
const view = this.props.user.hasSignedUp |
||||
|
? (<BoardContainer/>) //This may become multiple boards
|
||||
|
: (<SignUpContainer/>); |
||||
|
|
||||
|
return ( |
||||
|
<div> |
||||
|
{view} |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
user: state.user |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const HomeContainer = drizzleConnect(Home, mapStateToProps); |
||||
|
|
||||
|
export default HomeContainer; |
@ -0,0 +1,46 @@ |
|||||
|
import React from 'react'; |
||||
|
import { drizzleConnect } from 'drizzle-react'; |
||||
|
import { Route } from "react-router"; |
||||
|
import { Redirect } from "react-router-dom"; |
||||
|
import { withRouter } from 'react-router-dom'; |
||||
|
import { connect } from 'react-redux' |
||||
|
|
||||
|
const PrivateRoute = ({ component, redirectTo, user, ...rest }) => { |
||||
|
console.log("rest"); |
||||
|
console.log(JSON.stringify(component)); |
||||
|
return ( |
||||
|
<Route |
||||
|
{...rest} render={routeProps => { |
||||
|
return user.hasSignedUp |
||||
|
? (renderMergedProps(component, routeProps, rest)) |
||||
|
: (<Redirect |
||||
|
to={{ |
||||
|
pathname: redirectTo, |
||||
|
state: { from: routeProps.location } |
||||
|
}} |
||||
|
/>); |
||||
|
}}/> |
||||
|
); |
||||
|
}; |
||||
|
|
||||
|
const renderMergedProps = (component, ...rest) => { |
||||
|
const finalProps = Object.assign({}, ...rest); |
||||
|
return ( |
||||
|
React.createElement(component, finalProps) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
const PrivateRouteContainer = withRouter(connect(state => ({ |
||||
|
hasSignedUp: state.user.hasSignedUp |
||||
|
}))(PrivateRoute)); |
||||
|
|
||||
|
/*const mapStateToProps = state => { |
||||
|
return { |
||||
|
user: state.user |
||||
|
} |
||||
|
};*/ |
||||
|
|
||||
|
/*const PrivateRouteContainer = withRouter(drizzleConnect(PrivateRoute, mapStateToProps));*/ |
||||
|
|
||||
|
export default PrivateRouteContainer; |
@ -0,0 +1,35 @@ |
|||||
|
import { drizzleConnect } from 'drizzle-react'; |
||||
|
import React, { Component } from 'react'; |
||||
|
|
||||
|
import ProfileInformation from '../components/ProfileInformation'; |
||||
|
import PostList from '../components/PostList'; |
||||
|
|
||||
|
class SignUp extends Component { |
||||
|
render() { |
||||
|
return ( |
||||
|
<div className="pure-g"> |
||||
|
<ProfileInformation username={this.props.user.username} |
||||
|
address={this.props.user.address} |
||||
|
orbitAddress={this.props.orbitDB.id}/> |
||||
|
<p className="pure-u-1-1"> |
||||
|
My posts: |
||||
|
</p> |
||||
|
<PostList/> {/*TODO change this with actual user's posts*/} |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
accounts: state.accounts, |
||||
|
Forum: state.contracts.Forum, |
||||
|
user: state.user, |
||||
|
orbitDB: state.orbitDB, |
||||
|
drizzleStatus: state.drizzleStatus |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const ProfileContainer = drizzleConnect(SignUp, mapStateToProps); |
||||
|
|
||||
|
export default ProfileContainer; |
@ -0,0 +1,34 @@ |
|||||
|
import { drizzleConnect } from 'drizzle-react'; |
||||
|
import React, { Component } from 'react'; |
||||
|
import UsernameFormContainer from './UsernameFormContainer'; |
||||
|
|
||||
|
class SignUp extends Component { |
||||
|
render() { |
||||
|
return ( |
||||
|
<div className="container pure-g"> |
||||
|
<div className="pure-u-1-1"> |
||||
|
<h1>Sign Up</h1> |
||||
|
<p><strong>Username</strong>: {this.props.user.username}</p> |
||||
|
<p><strong>Account</strong>: {this.props.user.address}</p> |
||||
|
<p><strong>OrbitDB</strong>: {this.props.orbitDB.id}</p> |
||||
|
<UsernameFormContainer/> |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// May still need this even with data function to refresh component on updates for this contract.
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
accounts: state.accounts, |
||||
|
Forum: state.contracts.Forum, |
||||
|
user: state.user, |
||||
|
orbitDB: state.orbitDB, |
||||
|
drizzleStatus: state.drizzleStatus |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const SignUpContainer = drizzleConnect(SignUp, mapStateToProps); |
||||
|
|
||||
|
export default SignUpContainer; |
@ -0,0 +1,54 @@ |
|||||
|
import { drizzleConnect } from 'drizzle-react'; |
||||
|
import React, { Component } from 'react'; |
||||
|
import PostList from '../components/PostList'; |
||||
|
import NewPost from '../components/NewPost'; |
||||
|
import FloatingButton from '../components/FloatingButton'; |
||||
|
|
||||
|
class Topic extends Component { |
||||
|
constructor(props) { |
||||
|
super(props); |
||||
|
|
||||
|
this.state = { |
||||
|
posting: false |
||||
|
}; |
||||
|
|
||||
|
this.handleClick = this.handleClick.bind(this); |
||||
|
} |
||||
|
|
||||
|
handleClick(event) { |
||||
|
if (event){ |
||||
|
event.preventDefault(); |
||||
|
} |
||||
|
this.setState(prevState => ({ |
||||
|
posting: !prevState.posting |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
render() { |
||||
|
return ( |
||||
|
this.state.posting |
||||
|
?(<div style={{marginBottom: '100px'}}> |
||||
|
<PostList/> |
||||
|
<NewPost onCancelClick={() => {this.handleClick()}}/> |
||||
|
</div>) |
||||
|
:(<div style={{marginBottom: '100px'}}> |
||||
|
<PostList/> |
||||
|
<FloatingButton onClick={this.handleClick}/> |
||||
|
</div>) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const mapStateToProps = state => { |
||||
|
return { |
||||
|
accounts: state.accounts, |
||||
|
Forum: state.contracts.Forum, |
||||
|
user: state.user, |
||||
|
orbitDB: state.orbitDB, |
||||
|
drizzleStatus: state.drizzleStatus |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
const TopicContainer = drizzleConnect(Topic, mapStateToProps); |
||||
|
|
||||
|
export default TopicContainer; |
@ -1,110 +0,0 @@ |
|||||
/* PAGE */ |
|
||||
|
|
||||
body, |
|
||||
.pure-g [class*=pure-u] { |
|
||||
font-family: 'Open Sans', sans-serif; |
|
||||
} |
|
||||
|
|
||||
.header { |
|
||||
text-align: center; |
|
||||
} |
|
||||
|
|
||||
h1, h2, h3 { |
|
||||
font-family: 'Oswald', 'Arial Narrow', sans-serif; |
|
||||
} |
|
||||
|
|
||||
code { |
|
||||
padding: .25rem; |
|
||||
margin: 0 .25rem; |
|
||||
background: #eee; |
|
||||
} |
|
||||
|
|
||||
.container { |
|
||||
box-sizing: border-box; |
|
||||
width: calc(100% - 60px); |
|
||||
max-width: 600px; |
|
||||
padding: 45px 20px; |
|
||||
margin: 0 auto; |
|
||||
} |
|
||||
|
|
||||
.pure-button-primary { |
|
||||
background-color: #0c1a2b; |
|
||||
} |
|
||||
|
|
||||
.pure-button-primary:hover { |
|
||||
background-color: #233e5e; |
|
||||
} |
|
||||
|
|
||||
.pure-form input[type="text"]:focus { |
|
||||
border-color: #0c1a2b; |
|
||||
} |
|
||||
|
|
||||
ul { |
|
||||
list-style-type: none; |
|
||||
padding-left: 0; |
|
||||
} |
|
||||
|
|
||||
ul li:not(:last-child) { |
|
||||
margin-bottom: 15px; |
|
||||
} |
|
||||
|
|
||||
/* NAVBAR */ |
|
||||
|
|
||||
.navbar { |
|
||||
position: fixed; |
|
||||
padding: 5px; |
|
||||
background: #0c1a2b; |
|
||||
font-family: 'Oswald', 'Arial Narrow', sans-serif; |
|
||||
} |
|
||||
|
|
||||
.navbar a { |
|
||||
color: #fff; |
|
||||
} |
|
||||
|
|
||||
.navbar a:active, |
|
||||
.navbar a:focus, |
|
||||
.navbar a:hover { |
|
||||
background: #233e5e; |
|
||||
} |
|
||||
|
|
||||
.navbar .pure-menu-heading { |
|
||||
font-weight: bold; |
|
||||
text-transform: none; |
|
||||
} |
|
||||
|
|
||||
.navbar .navbar-right { |
|
||||
float: right; |
|
||||
} |
|
||||
|
|
||||
.navbar .uport-logo { |
|
||||
height: 16px; |
|
||||
margin-right: 10px; |
|
||||
} |
|
||||
|
|
||||
/* LOADING SCREEN */ |
|
||||
|
|
||||
.loading-screen { |
|
||||
opacity: 1; |
|
||||
visibility: visible; |
|
||||
transition: all .25s ease-in-out; |
|
||||
} |
|
||||
|
|
||||
.loading-screen.loaded { |
|
||||
opacity: 0; |
|
||||
visibility: hidden; |
|
||||
} |
|
||||
|
|
||||
/* FORMS */ |
|
||||
|
|
||||
.pure-form { |
|
||||
display: inline-block; |
|
||||
} |
|
||||
|
|
||||
/* ALERTS */ |
|
||||
|
|
||||
.alert { |
|
||||
padding: .5rem; |
|
||||
color: darkgreen; |
|
||||
background: darkseagreen; |
|
||||
border: 1px solid darkgreen; |
|
||||
} |
|
@ -0,0 +1,26 @@ |
|||||
|
import React, { Component } from 'react'; |
||||
|
import HeaderBar from '../../components/HeaderBar'; |
||||
|
import NavBar from '../../components/NavBar'; |
||||
|
|
||||
|
// Styles
|
||||
|
import '../../assets/fonts/fontawesome-free-5.0.13/fontawesome-all.js'; |
||||
|
import '../../assets/css/oswald.css'; |
||||
|
import '../../assets/css/open-sans.css'; |
||||
|
import '../../assets/css/pure-min.css'; |
||||
|
import '../../assets/css/App.css'; |
||||
|
|
||||
|
class CoreLayout extends Component { |
||||
|
render() { |
||||
|
return ( |
||||
|
<div className="App"> |
||||
|
<HeaderBar/> |
||||
|
<NavBar/> |
||||
|
<div className="view-container"> |
||||
|
{this.props.children} |
||||
|
</div> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export default CoreLayout; |
@ -1,41 +0,0 @@ |
|||||
import { drizzleConnect } from 'drizzle-react' |
|
||||
import React, { Component } from 'react' |
|
||||
import UsernameFormContainer from '../../containers/UsernameFormContainer' |
|
||||
|
|
||||
class Home extends Component { |
|
||||
render() { |
|
||||
return ( |
|
||||
<main className="container"> |
|
||||
<div className="pure-g"> |
|
||||
<div className="pure-u-1-1 header"> |
|
||||
<h1>Apella</h1> |
|
||||
<br/><br/> |
|
||||
</div> |
|
||||
<div className="pure-u-1-1"> |
|
||||
<h2>Account</h2> |
|
||||
<p><strong>Username</strong>: {this.props.user.username}</p> |
|
||||
<p><strong>Account</strong>: {this.props.user.address}</p> |
|
||||
<p><strong>OrbitDB</strong>: {this.props.orbitDB.id}</p> |
|
||||
<UsernameFormContainer/> |
|
||||
<br/><br/> |
|
||||
</div> |
|
||||
</div> |
|
||||
</main> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// May still need this even with data function to refresh component on updates for this contract.
|
|
||||
const mapStateToProps = state => { |
|
||||
return { |
|
||||
accounts: state.accounts, |
|
||||
Forum: state.contracts.Forum, |
|
||||
user: state.user, |
|
||||
orbitDB: state.orbitDB, |
|
||||
drizzleStatus: state.drizzleStatus |
|
||||
} |
|
||||
}; |
|
||||
|
|
||||
const HomeContainer = drizzleConnect(Home, mapStateToProps); |
|
||||
|
|
||||
export default HomeContainer |
|
After Width: | Height: | Size: 70 KiB |
After Width: | Height: | Size: 216 KiB |