@ -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 |