Initial git

This commit is contained in:
Qolzam
2017-07-06 11:20:18 +04:30
commit 7c691d8e4d
142 changed files with 13046 additions and 0 deletions

271
app/components/Blog.jsx Normal file
View File

@@ -0,0 +1,271 @@
// - Import react components
import React, { Component } from 'react'
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { Card, CardActions, CardHeader, CardMedia, CardTitle, CardText } from 'material-ui/Card'
import FlatButton from 'material-ui/FlatButton'
import { grey400, grey800, darkBlack, lightBlack } from 'material-ui/styles/colors'
import SvgCamera from 'material-ui/svg-icons/image/photo-camera'
import Paper from 'material-ui/Paper'
import { List, ListItem } from 'material-ui/List'
// - Import app components
import Post from 'Post'
import PostWrite from 'PostWrite'
import UserAvatar from 'UserAvatar'
// - Import API
import * as AuthAPI from 'AuthAPI'
import * as PostAPI from 'PostAPI'
// - Import actions
import * as globalActions from 'globalActions'
// - Create Blog component class
export class Blog extends Component {
static propTypes = {
/**
* If it's true , writing post block will be visible
*/
displayWriting: PropTypes.bool.isRequired,
/**
* A list of post
*/
posts: PropTypes.object,
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
this.state = {
/**
* It's true if we want to have two column of posts
*/
divided: false,
/**
* If it's true comment will be disabled on post
*/
disableComments: this.props.disableComments,
/**
* If it's true share will be disabled on post
*/
disableSharing: this.props.disableSharing,
/**
* If it's true, post write will be open
*/
openPostWrite: false,
/**
* The title of home header
*/
homeTitle: PropTypes.string
}
// Binding functions to `this`
this.postLoad = this.postLoad.bind(this)
this.handleOpenPostWrite = this.handleOpenPostWrite.bind(this)
this.handleClosePostWrite = this.handleClosePostWrite.bind(this)
}
/**
* Open post write
*
*
* @memberof Blog
*/
handleOpenPostWrite = () => {
this.setState({
openPostWrite: true
})
}
/**
* Close post write
*
*
* @memberof Blog
*/
handleClosePostWrite = () => {
this.setState({
openPostWrite: false
})
}
/**
* Create a list of posts
* @return {DOM} posts
*/
postLoad = () => {
let { posts,match } = this.props
let {tag} = match.params
if (posts === undefined || !Object.keys(posts).length > 0) {
return (
<h1>
'Nothing has shared.'
</h1>
)
}
else {
var postBack = { oddPostList: [], evenPostList: [] }
var parsedPosts = []
Object.keys(posts).forEach((postId) => {
if(tag){
let regex = new RegExp("#" + tag,'g')
let postMatch = posts[postId].body.match(regex)
if(postMatch !==null)
parsedPosts.push({ ...posts[postId]})
}else{
parsedPosts.push({ ...posts[postId]})
}
})
const sortedPosts = PostAPI.sortObjectsDate(parsedPosts)
if (sortedPosts.length > 6) {
postBack.divided = true
} else {
postBack.divided = false
}
sortedPosts.forEach((post, index) => {
var newPost = (
<div key={post.id}>
{index > 1 || (!postBack.divided && index > 0) ? <div style={{ height: "16px" }}></div> : ''}
<Post
body={post.body}
commentCounter={post.commentCounter}
creationDate={post.creationDate}
id={post.id}
image={post.image}
lastEditDate={post.lastEditDate}
ownerDisplayName={post.ownerDisplayName}
ownerUserId={post.ownerUserId}
ownerAvatar={post.ownerAvatar}
postTypeId={post.postTypeId}
score={post.score}
tags={post.tags}
video={post.video}
disableComments={post.disableComments}
disableSharing={post.disableSharing}
viewCount={posts.viewCount}
pictureState={true} />
</div>
)
if ((index % 2) === 1 && postBack.divided) {
postBack.oddPostList.push(newPost)
}
else {
postBack.evenPostList.push(newPost)
}
})
return postBack
}
}
componentWillMount() {
const {setHomeTitle} = this.props
setHomeTitle()
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
let postList = this.postLoad()
const {tag, displayWriting, } = this.props
return (
<div >
<div className='grid grid__gutters grid__1of2 grid__space-around animate-top'>
<div className='grid-cell animate-top' style={{maxWidth: '530px', minWidth: '280px'}}>
{displayWriting && !tag
? (<PostWrite open={this.state.openPostWrite} onRequestClose={this.handleClosePostWrite} edit={false} >
<Paper zDepth={2} style={{ height: "68px", width: "100%" }}>
<ListItem
primaryText={<span style={{ color: grey400, cursor: "text" }}> What's new with you? </span>}
leftAvatar={<UserAvatar fileName={this.props.avatar} size={36} />}
rightIcon={<SvgCamera />}
style={{ padding: "7px 0px", fontWeight: "200" }}
onTouchTap={this.handleOpenPostWrite}
/>
</Paper>
<div style={{ height: "16px" }}></div>
</PostWrite>)
: ''}
{postList.evenPostList}
<div style={{ height: "16px" }}></div>
</div>
{postList.divided
? (<div className='grid-cell animate-top' style={{maxWidth: '530px', minWidth: '280px'}}>
<div className="blog__right-list">
{postList.oddPostList}
<div style={{ height: "16px" }}></div>
</div>
</div>)
: ''}
</div>
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
setHomeTitle: () => dispatch(globalActions.setHeaderTitle(ownProps.homeTitle || '')),
showTopLoading: () => dispatch(globalActions.showTopLoading()),
hideTopLoading: () => dispatch(globalActions.hideTopLoading())
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : '',
}
}
// - Connect component to redux store
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Blog))

266
app/components/Circle.jsx Normal file
View File

@@ -0,0 +1,266 @@
// - Import react components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import {push} from 'react-router-redux'
import {grey400, darkBlack, lightBlack} from 'material-ui/styles/colors'
import { List, ListItem } from 'material-ui/List'
import SvgGroup from 'material-ui/svg-icons/action/group-work'
import IconButton from 'material-ui/IconButton'
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'
import IconMenu from 'material-ui/IconMenu'
import TextField from 'material-ui/TextField'
import MenuItem from 'material-ui/MenuItem'
import IconButtonElement from 'IconButtonElement'
import Dialog from 'material-ui/Dialog'
import Divider from 'material-ui/Divider'
import FlatButton from 'material-ui/FlatButton'
import RaisedButton from 'material-ui/RaisedButton'
import SvgClose from 'material-ui/svg-icons/navigation/close'
import AppBar from 'material-ui/AppBar'
// - Import app components
import UserAvatar from 'UserAvatar'
// - Import API
// - Import actions
import * as circleActions from 'circleActions'
/**
* Create component class
*/
export class Circle extends Component {
static propTypes = {
/**
* Circle object
*/
circle: PropTypes.object.isRequired,
/**
* Circle identifier
*/
id: PropTypes.string.isRequired
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
//Defaul state
this.state = {
/**
* If is true circle is open to show users in circle list
*/
open:false,
/**
* Circle name on change
*/
circleName: this.props.circle.name,
/**
* Save operation will be disable if user doesn't meet requirement
*/
disabledSave: false
}
// Binding functions to `this`
this.handleToggleCircle = this.handleToggleCircle.bind(this)
this.handleDeleteCircle = this.handleDeleteCircle.bind(this)
this.handleUpdateCircle = this.handleUpdateCircle.bind(this)
this.handleChangeCircleName = this.handleChangeCircleName.bind(this)
}
/**
* Handle chage circle name
*
*
* @memberof Circle
*/
handleChangeCircleName = (evt) => {
const {value} = evt.target
this.setState({
circleName: value,
disabledSave: (!value || value.trim() === '')
})
}
/**
* Update user's circle
*
*
* @memberof Circle
*/
handleUpdateCircle = () => {
const {circleName} = this.state
if(circleName && circleName.trim() !== ''){
this.props.updateCircle({name:circleName,id: this.props.id})
}
}
/**
* Handle delete circle
*
*
* @memberof Circle
*/
handleDeleteCircle = () => {
this.props.deleteCircle(this.props.id)
}
/**
* Toggle circle to close/open
*
*
* @memberof Circle
*/
handleToggleCircle = () => {
this.setState({
open: !this.state.open
})
}
userList = () => {
const {users} = this.props.circle
const {userInfo} = this.props
let usersParsed =[]
if(users){
Object.keys(users).forEach((key, index) => {
const { fullName} = users[key]
let avatar = userInfo && userInfo[key] ? userInfo[key].avatar || '' : ''
usersParsed.push(<ListItem
key={`${this.props.id}.${key}`}
style={{backgroundColor: '#e2e2e2'}}
value={2}
primaryText={fullName}
leftAvatar={<UserAvatar fileName={avatar}/>}
onClick={()=> this.props.goTo(`/${key}`)}
/>)
})
return usersParsed
}
}
/**
* Right icon menue of circle
*
*
* @memberof Circle
*/
rightIconMenu =(
<IconMenu iconButtonElement={IconButtonElement} style={{ display: "block", position: "absolute", top: "0px", right: "12px" }}>
<MenuItem primaryText="Delete circle" onClick={this.handleDeleteCircle} />
<MenuItem primaryText="Circle settings" onClick={this.props.openCircleSettings} />
</IconMenu>
)
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
const circleTitle =(
<div>
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: "space-between" }}>
<div style={{paddingRight: '10px'}}>
<SvgClose onClick={this.props.closeCircleSettings} hoverColor={grey400} style={{cursor: 'pointer'}} />
</div>
<div style={{
color: 'rgba(0,0,0,0.87)',
flex: '1 1',
font: '500 20px Roboto,RobotoDraft,Helvetica,Arial,sans-serif'
}}>
Circle settings
</div>
<div style={{marginTop: '-9px'}}>
<FlatButton label="SAVE" primary={true} disabled={this.state.disabledSave} onClick={this.handleUpdateCircle} />
</div>
</div>
<Divider />
</div>
)
return (
<div>
<ListItem
key={this.props.id}
style={{backgroundColor: '#fff', borderBottom: '1px solid rgba(0,0,0,0.12)',height: '72px',padding: '12px 0' }}
primaryText={<span style={{color:'rgba(0,0,0,0.87)',fontSize:'16px',marginRight:'8px',whiteSpace:'nowrap',textOverflow:'ellipsis',overflow:'hidden'}}>{this.props.circle.name}</span>}
leftIcon={<SvgGroup style={{width:'40px',height:'40px',transform: 'translate(0px, -9px)',fill:'#bdbdbd'}} />}
rightIconButton={this.rightIconMenu}
initiallyOpen={false}
onClick={this.handleToggleCircle}
open={this.state.open}
nestedItems={this.userList()}
>
<Dialog
id={this.props.id}
title={circleTitle}
modal={false}
open={this.props.openSetting}
onRequestClose={this.props.closeCircleSettings}
overlayStyle={{ background: "rgba(0,0,0,0.12)" }}
contentStyle={{maxWidth: '400px'}}
>
<div>
<TextField
hintText="Circle name"
floatingLabelText="Circle name"
onChange={this.handleChangeCircleName}
value={this.state.circleName}
/>
</div>
</Dialog>
</ListItem>
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
let {uid} = ownProps
return {
deleteCircle: (id) => dispatch(circleActions.dbDeleteCircle(id)),
updateCircle: (circle) => dispatch(circleActions.dbUpdateCircle(circle)),
closeCircleSettings: () => dispatch(circleActions.closeCircleSettings(uid,ownProps.id)),
openCircleSettings: () => dispatch(circleActions.openCircleSettings(uid,ownProps.id)),
goTo: (url)=> dispatch(push(url))
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
let {uid} = state.authorize
return {
openSetting: state.circle ? (state.circle.userCircles[uid] ? (state.circle.userCircles[uid][ownProps.id].openCircleSettings || false) : false ) : false,
userInfo: state.user.info
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(Circle)

314
app/components/Comment.jsx Normal file
View File

@@ -0,0 +1,314 @@
// - Import react components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { NavLink} from 'react-router-dom'
import PropTypes from 'prop-types'
import { createAction as action } from 'redux-actions'
import moment from 'moment'
import { List, ListItem } from 'material-ui/List'
import Divider from 'material-ui/Divider'
import Paper from 'material-ui/Paper'
import FlatButton from 'material-ui/FlatButton'
import { grey400, darkBlack, lightBlack } from 'material-ui/styles/colors'
import IconButton from 'material-ui/IconButton'
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'
import IconMenu from 'material-ui/IconMenu'
import MenuItem from 'material-ui/MenuItem'
import TextField from 'material-ui/TextField'
// - Import app components
import UserAvatar from 'UserAvatar'
// - Import API
// - Import action types
import * as types from 'actionTypes'
// - Import actions
import * as commentActions from 'commentActions'
import * as userActions from 'userActions'
/**
* Create component class
*/
export class Comment extends Component {
static propTypes = {
/**
* Comment object
*/
comment: PropTypes.object,
/**
* If it's true the post owner is the logged in user which this post be long to the comment
*/
isPostOwner: PropTypes.bool,
/**
* If it's true the comment is disable to write
*/
disableComments: PropTypes.bool,
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
this.textareaRef = i => { this.inputText = i }
this.divCommentRef = i => { this.divComment = i }
//Defaul state
this.state = {
/**
* Comment text
*/
text: this.props.comment.text,
/**
* Comment text to match edit with new comment that is edited
*/
initialText: this.props.comment.text,
/**
* If comment text dosn't take any change it will be true
*/
editDisabled: true,
/**
* If it's true the post owner is the logged in user which this post be long to the comment
*/
isPostOwner: PropTypes.bool
}
// Binding functions to `this`
this.handleDelete = this.handleDelete.bind(this)
this.handleUpdateComment = this.handleUpdateComment.bind(this)
this.handleOnChange = this.handleOnChange.bind(this)
this.handleCancelEdit = this.handleCancelEdit.bind(this)
this.handleEditComment = this.handleEditComment.bind(this)
}
/**
* Handle show edit comment
* @param {event} evt is an event passed by clicking on edit button
*/
handleEditComment = (evt) => {
this.inputText.style.height = this.divComment.clientHeight + 'px'
this.props.openEditor()
}
/**
* Handle cancel edit
* @param {event} evt is an event passed by clicking on cancel button
*/
handleCancelEdit = (evt) => {
this.setState({
text: this.state.initialText
})
this.props.closeEditor()
}
/**
* Handle edit comment
* @param {event} evt is an event passed by clicking on post button
*/
handleUpdateComment = (evt) => {
this.props.update(this.props.comment.id, this.props.comment.postId, this.state.text)
this.setState({
initialText: this.state.text
})
}
/**
* When comment text changed
* @param {event} evt is an event passed by change comment text callback funciton
* @param {string} data is the comment text which user writes
*/
handleOnChange = (evt) => {
const data = evt.target.value
this.inputText.style.height = evt.target.scrollHeight + 'px'
if (data.length === 0 || data.trim() === '' || data.trim() === this.state.initialText) {
this.setState({
text: data,
editDisabled: true
})
}
else {
this.setState({
text: data,
editDisabled: false
})
}
}
/**
* Delete a comment
* @param {event} evt an event passed by click on delete comment
* @param {string} id comment identifire
* @param {string} postId post identifier which comment belong to
*/
handleDelete = (evt, id, postId) => {
this.props.delete(id, postId)
}
componentWillMount() {
const {userId} = this.props.comment
if (!this.props.isCommentOwner && !this.props.info[userId]) {
this.props.getUserInfo()
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
/**
* DOM styles
*
*
* @memberof Comment
*/
const styles = {
comment: {
marginBottom: '12px'
},
iconButton: {
width: 16,
height: 16
},
author:{
fontSize: "13px",
paddingRight: "10px",
fontWeight: 400,
color: "rgba(0,0,0,0.87)",
textOverflow: "ellipsis",
overflow: "hidden"
},
commentBody:{
fontSize: "13px",
lineHeight: "20px",
color: "rgba(0,0,0,0.87)",
fontWeight: 300,
height: "",
display: "block"
}
}
/**
* Comment object from props
*/
const {comment} = this.props
const iconButtonElement = (
<IconButton style={styles.iconButton} iconStyle={styles.iconButton}
touch={true}
>
<MoreVertIcon color={grey400} viewBox='9 0 24 24' />
</IconButton>
)
const RightIconMenu = () => (
<IconMenu iconButtonElement={iconButtonElement} style={{ display: "block", position: "absolute", top: "0px", right: "4px" }}>
<MenuItem style={{ fontSize: "14px" }}>Reply</MenuItem>
{this.props.isCommentOwner ? (<MenuItem style={{ fontSize: "14px" }} onClick={this.handleEditComment}>Edit</MenuItem>): ''}
{(this.props.isCommentOwner || this.props.isPostOwner) ? ( <MenuItem style={{ fontSize: "14px" }} onClick={(evt) => this.handleDelete(evt, comment.id, comment.postId)}>Delete</MenuItem>): ''}
</IconMenu>
)
const Author = () => (
<div style={{ marginTop: "-11px" }}>
<span style={styles.author}>{comment.userDisplayName}</span><span style={{
fontWeight: 100,
fontSize: "10px"
}}>{moment.unix(comment.creationDate).fromNow()}</span>
</div>
)
const commentBody = (
<p style={styles.commentBody}>{comment.text}</p>
)
const {userId} = comment
return (
<div className="animate-top" style={styles.comment} key={comment.id}>
<Paper zDepth={0} className="animate2-top10" style={{ position: "relative", padding: "", display: (!this.state.display ? "block" : "none") }}>
<div style={{ marginLeft: "0px", padding: "16px 56px 0px 72px", position: "relative" }}>
<NavLink to={`/${userId}`}><UserAvatar fileName={this.props.avatar} style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", position: "absolute", top: "8px", left: "16px" }} size={36} /></NavLink>
<NavLink to={`/${userId}`}> <Author /></NavLink>
{(!this.props.isCommentOwner && !this.props.isPostOwner && this.props.disableComments )? '' : (<RightIconMenu />)}
<div style={{ outline: "none", marginLeft: "16px", flex: "auto", flexGrow: 1 }}>
<textarea ref={this.textareaRef} className="animate2-top10" style={{ fontWeight: 100, fontSize: "14px", border: "none", width: "100%", outline: "none", resize: "none", display: (this.props.comment.editorStatus ? 'block' : 'none') }} onChange={this.handleOnChange} value={this.state.text}></textarea>
<div ref={this.divCommentRef} className="animate2-top10" style={{ fontWeight: 100, fontSize: "14px", height: "100%", border: "none", width: "100%", outline: "none", resize: "none", display: (!this.props.comment.editorStatus ? 'block' : 'none') }}>{this.state.text}</div>
</div>
</div>
<div style={{ display: (this.props.comment.editorStatus ? "flex" : "none"), flexDirection: "row-reverse" }}>
<FlatButton primary={true} disabled={this.state.editDisabled} label="Update" style={{ float: "right", clear: "both", zIndex: 5, margin: "0px 5px 5px 0px", fontWeight: 400 }} onClick={this.handleUpdateComment} />
<FlatButton primary={true} label="Cancel" style={{ float: "right", clear: "both", zIndex: 5, margin: "0px 5px 5px 0px", fontWeight: 400 }} onClick={this.handleCancelEdit} />
</div>
</Paper>
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
delete: (id, postId) => dispatch(commentActions.dbDeleteComment(id, postId)),
update: (id, postId, comment) => dispatch(commentActions.dbUpdateComment(id, postId, comment)),
openEditor: () => dispatch(commentActions.openCommentEditor({ id: ownProps.comment.id, postId: ownProps.comment.postId })),
closeEditor: () => dispatch(commentActions.closeCommentEditor({ id: ownProps.comment.id, postId: ownProps.comment.postId })),
getUserInfo: () => dispatch(userActions.dbGetUserInfoByUserId(ownProps.comment.userId))
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
const {uid} = state.authorize
const avatar = state.user.info && state.user.info[ownProps.comment.userId] ? state.user.info[ownProps.comment.userId].avatar || '' : ''
return {
uid: uid,
isCommentOwner: (uid === ownProps.comment.userId),
info: state.user.info,
avatar
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(Comment)

View File

@@ -0,0 +1,270 @@
// - Import react components
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import Paper from 'material-ui/Paper'
import FlatButton from 'material-ui/FlatButton'
import TextField from 'material-ui/TextField'
import Divider from 'material-ui/Divider'
import { ListItem } from 'material-ui/List'
import { grey400, darkBlack, lightBlack } from 'material-ui/styles/colors'
// - Import actions
import * as commentActions from 'commentActions'
// - Import app components
import CommentList from 'CommentList'
import CommentWrite from 'CommentWrite'
import UserAvatar from 'UserAvatar'
/**
* Create component class
*/
export class CommentGroup extends Component {
static propTypes = {
/**
* If it's true comment box will be open
*/
open: PropTypes.bool,
/**
* If it's true the comment is disable to write
*/
disableComments: PropTypes.bool,
/**
* The post identifier which comment belong to
*/
postId: PropTypes.string,
/**
* If it's true the post owner is the logged in user which this post be long to the comment
*/
isPostOwner: PropTypes.bool,
/**
* Toggle on show/hide comment by passing callback from parent component
*/
onToggleRequest: PropTypes.func,
/**
* The user identifier of the post owner which comment belong to
*/
ownerPostUserId:PropTypes.string
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
/**
* Defaul state
*/
this.state = {
commentText: "",
postDisable: true
}
// Binding functions to `this`
this.commentList = this.commentList.bind(this)
this.handlePostComment = this.handlePostComment.bind(this)
this.clearCommentWrite = this.clearCommentWrite.bind(this)
}
/**
* Clear comment text field
*/
clearCommentWrite = () => {
this.setState({
commentText: '',
postDisable: false
})
}
/**
* Post comment
*/
handlePostComment = () => {
this.props.send(this.state.commentText, this.props.postId, this.clearCommentWrite)
}
/**
* When comment text changed
* @param {event} evt is an event passed by change comment text callback funciton
* @param {string} data is the comment text which user writes
*/
handleOnChange = (evt, data) => {
this.setState({ commentText: data })
if (data.length === 0 || data.trim() === '') {
this.setState({
commentText: '',
postDisable: true
})
}
else {
this.setState({
commentText: data,
postDisable: false
})
}
}
/**
* Get comments' DOM
* @return {DOM} list of comments' DOM
*/
commentList = () => {
var comments = this.props.comments
if (comments) {
var parsedComments = [];
Object.keys(comments).slice(0, 3).forEach((commentId) => {
parsedComments.push({
id: commentId,
...comments[commentId]
});
});
if (parsedComments.length === 2) {
parsedComments.push(parsedComments[0])
}
else if (parsedComments.length === 1) {
parsedComments.push(parsedComments[0])
parsedComments.push(parsedComments[0])
}
return parsedComments.map((comment, index) => {
const {userInfo} = this.props
const commentAvatar = userInfo && userInfo[comment.userId] ? userInfo[comment.userId].avatar || '' : ''
return (<ListItem key={index} style={{ height: "60px", position: "", zIndex: "" }} innerDivStyle={{ padding: "6px 16px 16px 72px" }}
leftAvatar={<UserAvatar fileName={commentAvatar} style={{ top: "8px" }} size={36} />}
secondaryText={<div style={{ height: "" }}>
<span style={{
fontSize: "13px",
paddingRight: "10px",
fontWeight: 400,
color: "rgba(0,0,0,0.87)",
textOverflow: "ellipsis",
overflow: "hidden"
}}>
{comment.userDisplayName}:
</span>
<span style={{
fontSize: "13px",
lineHeight: "20px",
color: "rgba(0,0,0,0.87)",
fontWeight: 300,
whiteSpace: "pre-wrap"
}}>{comment.text}</span>
</div>}
secondaryTextLines={2}
/>
)
})
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
return (
<div>
<div style={this.props.comments && Object.keys(this.props.comments).length > 0 ? { display: "block" } : { display: "none" }}>
<Divider />
<Paper zDepth={0} className="animate-top" style={!this.props.open ? { display: "block" } : { display: "none" }}>
<div style={{ position: "relative", height: "60px" }} >
<FlatButton label=" " style={{ height: "60px", zIndex: 5 }} fullWidth={true} onClick={this.props.onToggleRequest} />
<div className="comment__list-show">
{this.commentList()}
</div>
</div>
</Paper>
{(this.props.comments && Object.keys(this.props.comments).length > 0)
? (<Paper zDepth={0} style={this.props.open ? { display: "block", padding: "0px 0px" } : { display: "none", padding: "12px 16px" }}>
<CommentList comments={this.props.comments} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/>
</Paper>) : ''}
</div>
{!this.props.disableComments ? (<div>
<Divider />
<Paper zDepth={0} className="animate2-top10" style={{ position: "relative", overflowY: "auto", padding: "12px 16px", display: (this.props.open ? "block" : "none") }}>
<div style={{ display: "flex" }}>
<UserAvatar fileName={this.props.avatar} style={{ flex: "none", margin: "4px 0px" }} size={36} />
<div style={{ outline: "none", marginLeft: "16px", flex: "auto", flexGrow: 1 }}>
<TextField
value={this.state.commentText}
onChange={this.handleOnChange}
hintText="Add a comment..."
underlineShow={false}
multiLine={true}
rows={1}
hintStyle={{ fontWeight: 100, fontSize: "14px" }}
rowsMax={4}
textareaStyle={{ fontWeight: 100, fontSize: "14px" }}
style={{ width: '100%' }}
/>
</div>
</div>
<FlatButton primary={true} disabled={this.state.postDisable} label="Post" style={{ float: "right", clear: "both", zIndex: 5, margin: "0px 5px 5px 0px", fontWeight: 400 }} onClick={this.handlePostComment} />
</Paper>
</div>): ''}
</div>
);
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
send: (text, postId, callBack) => {
dispatch(commentActions.dbAddComment({
postId: postId,
text: text,
ownerPostUserId:ownProps.ownerPostUserId
}, callBack))
}
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
comments: state.comment.postComments[ownProps.postId],
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar || '' : '',
userInfo: state.user.info
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(CommentGroup)

View File

@@ -0,0 +1,133 @@
// - Import react components
import React, { Component } from 'react'
import { connect } from 'react-redux';
import PropTypes from 'prop-types'
import { List, ListItem } from 'material-ui/List'
// - Import app components
import Comment from 'Comment'
import * as PostAPI from 'PostAPI'
// - Import actions
/**
* Create component class
*/
export class CommentList extends Component {
static propTypes = {
/**
* If it's true the post owner is the logged in user which this post be long to the comment
*/
isPostOwner: PropTypes.bool,
/**
* If it's true the comment is disable to write
*/
disableComments: PropTypes.bool,
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
/**
* Default state
*/
this.state = {
}
// Binding functions to `this`
}
/**
* Get comments' DOM
* @return {DOM} list of comments' DOM
*/
commentList = () => {
var comments = this.props.comments
if (comments) {
var parsedComments = [];
Object.keys(comments).forEach((commentId) => {
parsedComments.push({
id: commentId,
...comments[commentId]
});
});
let sortedComments = PostAPI.sortObjectsDate(parsedComments)
return sortedComments.map((comment, index, array) => {
return <Comment key={comment.id} comment={comment} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/>
})
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
const styles = {
list: {
width: "100%",
maxHeight: 450,
overflowY: 'auto'
}
}
return (
<List style={styles.list}>
{this.commentList()}
</List>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state) => {
return {
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(CommentList)

View File

@@ -0,0 +1,84 @@
// - Import react components
import React, {Component} from 'react'
import {connect} from 'react-redux'
import Faker from 'faker'
// - Import app components
// - Import actions
import * as commentActions from 'commentActions'
// - Define variable
const buttonStyle = {
marginTop: '5px'
};
// - Create CommentWrite component class
export class CommentWrite extends Component {
constructor(props) {
super(props);
this.state = {
inputValue:''
}
// Binding functions to `this`
this.handleRef = this.handleRef.bind(this);
this.focus = this.focus.bind(this);
this.handleAddComment = this.handleAddComment.bind(this);
this.handleOnChange = this.handleOnChange.bind(this)
}
handleOnChange = (evt) =>{
this.setState({inputValue:evt.target.value})
}
handleRef = c => {
this.inputRef = c
}
focus = () => {
this.inputRef.focus()
}
handleAddComment = (evt) => {
this.props.send(this.state.inputValue,this.props.postId,this.props.close)
}
// Render DOM
render() {
return (
<div>
<textarea autoFocus defaultValue={this.props.commentText} onChange={this.handleOnChange}/>
<Button basic style={buttonStyle} onClick={this.handleAddComment} color='teal'>Add Comment</Button>
</div>
);
}
}
// - Map dispatch to props
const mapDispatchToProps = (dispatch,ownProps) => {
return{
send: (text,postId,callBack) => {
dispatch(commentActions.dbAddComment({
postId: postId,
text: text
},callBack))
}
}
}
// - Map state to props
const mapStateToProps = (state) => {
return{
}
}
// - Connect component to store
export default connect(mapStateToProps,mapDispatchToProps)(CommentWrite)

View File

@@ -0,0 +1,458 @@
// - Import react components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { grey400, darkBlack, lightBlack } from 'material-ui/styles/colors'
import IconButton from 'material-ui/IconButton'
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'
import SvgCamera from 'material-ui/svg-icons/image/photo-camera'
import IconMenu from 'material-ui/IconMenu'
import MenuItem from 'material-ui/MenuItem'
import FlatButton from 'material-ui/FlatButton'
import RaisedButton from 'material-ui/RaisedButton'
import EventListener, { withOptions } from 'react-event-listener'
import Dialog from 'material-ui/Dialog'
import Divider from 'material-ui/Divider'
import Paper from 'material-ui/Paper'
import TextField from 'material-ui/TextField'
// - Import app components
import ImgCover from 'ImgCover'
import DialogTitle from 'DialogTitle'
import ImageGallery from 'ImageGallery'
import FileAPI from 'FileAPI'
import UserAvatar from 'UserAvatar'
// - Import API
// - Import actions
import * as userActions from 'userActions'
import * as globalActions from 'globalActions'
import * as imageGalleryActions from 'imageGalleryActions'
/**
* Create component class
*/
export class EditProfile extends Component {
static propTypes = {
/**
* User avatar address
*/
avatar: PropTypes.string,
/**
* User avatar address
*/
banner: PropTypes.string,
/**
* User full name
*/
fullName: PropTypes.string.isRequired
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
//Defaul state
this.state = {
/**
* If it's true the winow is in small size
*/
isSmall: false,
/**
* User tag line input value
*/
tagLineInput: props.info.tagLine || '',
/**
* User full name input value
*/
fullNameInput: props.info.fullName || '',
/**
* Error message of full name input
*/
fullNameInputError: '',
/**
* User banner address
*/
banner: this.props.banner || '',
/**
* User avatar address
*/
avatar: this.props.avatar || '',
/**
* It's true if the image galley for banner is open
*/
openBanner: false,
/**
* It's true if the image gallery for avatar is open
*/
openAvatar: false
}
// Binding functions to `this`
this.handleChangeDate = this.handleChangeDate.bind(this)
this.handleUpdate = this.handleUpdate.bind(this)
this.handleRequestSetAvatar = this.handleRequestSetAvatar.bind(this)
this.handleRequestSetBanner = this.handleRequestSetBanner.bind(this)
}
/**
* Close image gallery of banner
*/
handleCloseBannerGallery = () => {
this.setState({
openBanner: false
})
}
/**
* Open image gallery of banner
*/
handleOpenBannerGallery = () => {
this.setState({
openBanner: true
})
}
/**
* Close image gallery of avatar
*/
handleCloseAvatarGallery = () => {
this.setState({
openAvatar: false
})
}
/**
* Open image gallery of avatar
*/
handleOpenAvatarGallery = () => {
this.setState({
openAvatar: true
})
}
/**
* Set banner image url
*/
handleRequestSetBanner = (url) => {
this.setState({
banner: url
})
}
/**
* Set avatar image url
*/
handleRequestSetAvatar = (fileName) => {
this.setState({
avatar: fileName
})
}
/**
* Update profile on the server
*
*
* @memberof EditProfile
*/
handleUpdate = () => {
const {fullNameInput, tagLineInput, avatar, banner} = this.state
if (this.state.fullNameInput.trim() === '') {
this.setState({
fullNameInputError: 'This field is required'
})
}
else {
this.setState({
fullNameInputError: ''
})
this.props.update({
fullName: fullNameInput,
tagLine: tagLineInput,
avatar: avatar,
banner: banner
})
}
}
/**
* Handle data on input change
* @param {event} evt is an event of inputs of element on change
*/
handleInputChange = (evt) => {
const target = evt.target
const value = target.type === 'checkbox' ? target.checked : target.value
const name = target.name
this.setState({
[name]: value
})
}
/**
* Handle change date
*/
handleChangeDate = (evt, date) => {
this.setState({
birthdayInput: date,
})
}
/**
* Handle resize event for window to change sidebar status
* @param {event} evt is the event is passed by winodw resize event
*/
handleResize = (evt) => {
// Set initial state
var width = window.innerWidth
if (width > 900) {
this.setState({
isSmall: false
})
}
else {
this.setState({
isSmall: true
})
}
}
componentDidMount = () => {
this.handleResize()
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
const styles = {
avatar: {
border: '2px solid rgb(255, 255, 255)'
},
paper: {
width: '90%',
height: '100%',
margin: '0 auto',
display: 'block',
},
title: {
padding: '24px 24px 20px 24px',
font: '500 20px Roboto,RobotoDraft,Helvetica,Arial,sans-serif',
display: 'flex',
wordWrap: 'break-word',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
flexGrow: 1
},
actions: {
display: 'flex',
justifyContent: "flex-end",
padding: '24px 24px 20px'
},
updateButton: {
marginLeft: '10px'
},
box: {
padding: '0px 24px 20px 24px',
display: 'flex'
},
dialogGallery: {
width: '',
maxWidth: '530px',
borderRadius: "4px"
}
}
const iconButtonElement = (
<IconButton style={this.state.isSmall ? styles.iconButtonSmall : styles.iconButton} iconStyle={this.state.isSmall ? styles.iconButtonSmall : styles.iconButton}
touch={true}
>
<MoreVertIcon color={grey400} viewBox='10 0 24 24' />
</IconButton>
)
const RightIconMenu = () => (
<IconMenu iconButtonElement={iconButtonElement}>
<MenuItem style={{ fontSize: "14px" }}>Reply</MenuItem>
<MenuItem style={{ fontSize: "14px" }}>Edit</MenuItem>
<MenuItem style={{ fontSize: "14px" }}>Delete</MenuItem>
</IconMenu>
)
return (
<div>
{/* Edit profile dialog */}
<Dialog
id='Edit-Profile'
modal={false}
open={this.props.open}
onRequestClose={this.props.onRequestClose}
autoScrollBodyContent={true}
bodyStyle={{ backgroundColor: "none", padding: 'none', borderTop: 'none', borderBottom: 'none' }}
overlayStyle={{ background: "rgba(0,0,0,0.12)" }}
contentStyle={{ backgroundColor: "none", maxWidth: '450px', maxHeight: 'none', height: '100%' }}
style={{ backgroundColor: "none", maxHeight: 'none', height: '100%' }}
>
{/* Banner */}
<div style={{ position: 'relative' }}>
<ImgCover width='100%' height='250px' borderRadius='2px' fileName={this.state.banner} />
<div className='g__circle-black' onClick={this.handleOpenBannerGallery} style={{ position: 'absolute', right: '10px', top: '10px' }}>
<SvgCamera style={{ fill: 'rgba(255, 255, 255, 0.88)', transform: 'translate(6px, 6px)' }} />
</div>
</div>
<div className='profile__edit'>
<EventListener
target="window"
onResize={this.handleResize}
/>
<div className='left'>
<div style={{ display: 'flex', justifyContent: 'center' }}>
{/* Avatar */}
<div className='g__circle-black' onClick={this.handleOpenAvatarGallery} style={{ position: 'absolute', left: '50%', display: 'inline-block', top: '52px', margin: '-18px' }}>
<SvgCamera style={{ fill: 'rgba(255, 255, 255, 0.88)', transform: 'translate(6px, 6px)' }} />
</div>
<UserAvatar fileName={this.state.avatar} size={90} style={styles.avatar} />
</div>
<div className='info'>
<div className='fullName'>
{this.props.fullName}
</div>
</div>
</div>
</div>
{/* Edit user information box*/}
<Paper style={styles.paper} zDepth={1}>
<div style={styles.title}>Personal Information</div>
<div style={styles.box}>
<TextField
floatingLabelText="Full name"
onChange={this.handleInputChange}
name='fullNameInput'
errorText={this.state.fullNameInputError}
value={this.state.fullNameInput}
/>
</div>
<br />
<div style={styles.box}>
<TextField
floatingLabelText="Tag Line"
onChange={this.handleInputChange}
name='tagLineInput'
value={this.state.tagLineInput}
/>
</div>
<br />
<div style={styles.actions}>
<FlatButton label="CANCEL" onClick={this.props.onRequestClose} />
<RaisedButton label="UPDATE" primary={true} onClick={this.handleUpdate} style={styles.updateButton} />
</div>
</Paper>
<div style={{ height: '16px' }}></div>
</Dialog>
{/* Image gallery for banner*/}
<Dialog
title={<DialogTitle title='Choose an banner image' onRequestClose={this.handleCloseBannerGallery} />}
modal={false}
open={this.state.openBanner}
contentStyle={styles.dialogGallery}
onRequestClose={this.handleCloseBannerGallery}
overlayStyle={{ background: "rgba(0,0,0,0.12)" }}
autoDetectWindowHeight={false}
>
<ImageGallery set={this.handleRequestSetBanner} close={this.handleCloseBannerGallery} />
</Dialog>
{/* Image gallery for avatar */}
<Dialog
title={<DialogTitle title='Choose an avatar image' onRequestClose={this.handleCloseAvatarGallery} />}
modal={false}
open={this.state.openAvatar}
contentStyle={styles.dialogGallery}
onRequestClose={this.handleCloseAvatarGallery}
overlayStyle={{ background: "rgba(0,0,0,0.12)" }}
autoDetectWindowHeight={false}
>
<ImageGallery set={this.handleRequestSetAvatar} close={this.handleCloseAvatarGallery} />
</Dialog>
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
update: (info) => dispatch(userActions.dbUpdateUserInfo(info)),
onRequestClose: () => dispatch(userActions.closeEditProfile()),
getImage: (name) => dispatch(imageGalleryActions.dbDownloadImage(name))
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
open: state.user.openEditProfile,
info: state.user.info[state.authorize.uid],
avatarURL: state.imageGallery.imageURLList
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(EditProfile)

View File

@@ -0,0 +1,110 @@
// - Import react components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import Paper from 'material-ui/Paper'
// - Import app components
import UserBoxList from 'UserBoxList'
// - Import API
// - Import actions
import * as userActions from 'userActions'
/**
* Create component class
*/
export class FindPeople extends Component {
static propTypes = {
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
//Defaul state
this.state = {
}
// Binding functions to `this`
}
componentWillMount() {
this.props.loadPeople()
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
const styles = {
paper: {
height: 254,
width: 243,
margin: 10,
textAlign: 'center',
maxWidth: '257px'
},
followButton:{
position: 'absolute',
bottom: '8px',
left: 0,
right: 0
}
}
return (
<div>
{this.props.peopleInfo && Object.keys(this.props.peopleInfo).length !== 0 ? (<div>
<div className='profile__title'>
Suggestions for you
</div>
<UserBoxList users={this.props.peopleInfo}/>
<div style={{ height: '24px' }}></div>
</div>) : (<div className='g__title-center'>
Nothing to show! :(
</div>)}
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
loadPeople: () => dispatch(userActions.dbGetPeopleInfo())
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
peopleInfo: state.user.info
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(FindPeople)

View File

@@ -0,0 +1,89 @@
// - Import react components
import React, {Component} from 'react'
import {connect} from 'react-redux'
import PropTypes from 'prop-types'
// - Import app components
import UserBoxList from 'UserBoxList'
// - Import API
// - Import actions
/**
* Create component class
*/
export class Followers extends Component {
static propTypes = {
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props){
super(props)
//Defaul state
this.state = {
}
// Binding functions to `this`
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
return (
<div>
{(this.props.followers && Object.keys(this.props.followers).length !==0) ? (<div>
<div className='profile__title'>
Followers
</div>
<UserBoxList users={this.props.followers} />
<div style={{ height: '24px' }}></div>
</div>)
: (<div className='g__title-center'>
No followers!
</div>)}
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch,ownProps) => {
return{
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state,ownProps) => {
const { uid } = state.authorize
const circles = state.circle ? state.circle.userCircles[uid] : {}
return{
followers: circles ? (circles['-Followers'] ? circles['-Followers'].users || {} : {}) : {}
}
}
// - Connect component to redux store
export default connect(mapStateToProps,mapDispatchToProps)(Followers)

View File

@@ -0,0 +1,95 @@
// - Import react components
import React, {Component} from 'react'
import {connect} from 'react-redux'
import PropTypes from 'prop-types'
// - Import app components
import UserBoxList from 'UserBoxList'
// - Import API
import CircleAPI from 'CircleAPI'
// - Import actions
/**
* Create component class
*/
export class Following extends Component {
static propTypes = {
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props){
super(props)
//Defaul state
this.state = {
}
// Binding functions to `this`
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
return (
<div>
{(this.props.followingUsers && Object.keys(this.props.followingUsers).length !== 0 )? (<div>
<div className='profile__title'>
Following
</div>
<UserBoxList users={this.props.followingUsers} />
<div style={{ height: '24px' }}></div>
</div>) : (<div className='g__title-center'>
No following user!
</div>)}
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch,ownProps) => {
return{
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state,ownProps) => {
const { uid } = state.authorize
const circles = state.circle ? state.circle.userCircles[uid] : {}
const followingUsers = CircleAPI.getFollowingUsers(circles)
return {
uid,
circles,
followingUsers,
}
}
// - Connect component to redux store
export default connect(mapStateToProps,mapDispatchToProps)(Following)

202
app/components/Home.jsx Normal file
View File

@@ -0,0 +1,202 @@
// - Import react components
import React, { Component } from 'react'
import _ from 'lodash'
import { Route, Switch, withRouter, Redirect, NavLink } from 'react-router-dom'
import { connect } from 'react-redux'
import { push } from 'react-router-redux'
import Menu from 'material-ui/Menu'
import MenuItem from 'material-ui/MenuItem'
import Divider from 'material-ui/Divider'
import SvgArrowLeft from 'material-ui/svg-icons/hardware/keyboard-arrow-left'
import SvgHome from 'material-ui/svg-icons/action/home'
import SvgFeedback from 'material-ui/svg-icons/action/feedback'
import SvgSettings from 'material-ui/svg-icons/action/settings'
import SvgAccountCircle from 'material-ui/svg-icons/action/account-circle'
import SvgPeople from 'material-ui/svg-icons/social/people'
// - Import app components
import Sidebar from 'Sidebar'
import Blog from 'Blog'
import HomeHeader from 'HomeHeader'
import SidebarContent from 'SidebarContent'
import SidebarMain from 'SidebarMain'
import Profile from 'Profile'
import PostPage from 'PostPage'
import People from 'People'
// - Import API
import CircleAPI from 'CircleAPI'
// - Import actions
import * as globalActions from 'globalActions'
// - Create Home component class
export class Home extends Component {
// Constructor
constructor(props) {
super(props)
// Default state
this.state = {
sidebarOpen: () => _,
sidebarStatus: true,
sidebaOverlay: false
}
// Binding function to `this`
this.sidebar = this.sidebar.bind(this)
this.sidebarStatus = this.sidebarStatus.bind(this)
this.sidebarOverlay = this.sidebarOverlay.bind(this)
this.handleCloseSidebar = this.handleCloseSidebar.bind(this)
}
/**
* handle close sidebar
*/
handleCloseSidebar = () => {
this.state.sidebarOpen(false, 'overlay')
}
/**
* Change sidebar overlay status
* @param {boolean} status if is true, the sidebar is on overlay status
*/
sidebarOverlay = (status) => {
this.setState({
sidebarOverlay: status
})
}
/**
* Pass function to change sidebar status
* @param {boolean} open is a function callback to change sidebar status out of sidebar component
*/
sidebar = (open) => {
this.setState({
sidebarOpen: open
})
}
/**
* Change sidebar status if is open or not
* @param {boolean} status is true, if the sidebar is open
*/
sidebarStatus = (status) => {
this.setState({
sidebarStatus: status
})
}
/**
* Render DOM component
*
* @returns DOM
*
* @memberof Home
*/
render() {
return (
<div id="home">
<HomeHeader sidebar={this.state.sidebarOpen} sidebarStatus={this.state.sidebarStatus} />
<Sidebar overlay={this.sidebarOverlay} open={this.sidebar} status={this.sidebarStatus}>
<SidebarContent>
<Menu style={{ color: "rgb(117, 117, 117)", width: '210px' }}>
{this.state.sidebarOverlay
? <div><MenuItem onClick={this.handleCloseSidebar} primaryText={<span style={{ color: "rgb(117, 117, 117)" }} className="sidebar__title">Green</span>} rightIcon={<SvgArrowLeft viewBox="0 3 24 24" style={{ color: "#fff", marginLeft: "15px", width: "32px", height: "32px", cursor: "pointer" }} />} /><Divider /></div>
: ""
}
<NavLink to='/'><MenuItem primaryText="Home" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgHome />} /></NavLink>
<NavLink to={`/${this.props.uid}`}><MenuItem primaryText="Profile" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgAccountCircle />} /></NavLink>
<NavLink to='/people'><MenuItem primaryText="People" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgPeople />} /></NavLink>
<Divider />
<NavLink to='/settings'><MenuItem primaryText="Settings" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgSettings />} /></NavLink>
<NavLink to='#'><MenuItem primaryText="Send feedback" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgFeedback />} /></NavLink>
</Menu>
</SidebarContent>
<SidebarMain>
<Switch>
<Route path="/people/:tab?" render={() => {
return (
this.props.authed
? <People />
: <Redirect to="/login" />
)
}} />
<Route path="/tag/:tag" render={({match}) => {
return (
this.props.authed
? <div className="blog"><Blog displayWriting={false} homeTitle={`#${match.params.tag}`} posts={this.props.mergedPosts} /></div>
: <Redirect to="/login" />
)
}} />
<Route path="/:userId/posts/:postId/:tag?" component={PostPage} />
<Route path="/:userId" component={Profile} />
<Route path="/" render={() => {
return (
this.props.authed
? <div className="blog"><Blog homeTitle='Home' posts={this.props.mergedPosts} displayWriting={true} /></div>
: <Redirect to="/login" />
)
}} />
</Switch>
</SidebarMain>
</Sidebar>
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
const { uid } = state.authorize
let mergedPosts = {}
const circles = state.circle ? (state.circle.userCircles[uid] || {}) : {}
const followingUsers = CircleAPI.getFollowingUsers(circles)
const posts = state.post.userPosts ? state.post.userPosts[state.authorize.uid] : {}
Object.keys(followingUsers).forEach((userId)=>{
let newPosts = state.post.userPosts ? state.post.userPosts[userId] : {}
_.merge(mergedPosts,newPosts)
})
_.merge(mergedPosts,posts)
return {
authed: state.authorize.authed,
mainStyle: state.global.sidebarMainStyle,
mergedPosts
}
}
// - Connect component to redux store
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Home))

View File

@@ -0,0 +1,285 @@
// - Import react components
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'
import { connect } from 'react-redux'
import SvgDehaze from 'material-ui/svg-icons/image/dehaze'
import { green700, grey400, blue500 } from 'material-ui/styles/colors'
import { Toolbar, ToolbarGroup, ToolbarSeparator, ToolbarTitle } from 'material-ui/Toolbar'
import IconButton from 'material-ui/IconButton'
import RaisedButton from 'material-ui/RaisedButton'
import Popover, { PopoverAnimationVertical } from 'material-ui/Popover'
import Menu from 'material-ui/Menu'
import MenuItem from 'material-ui/MenuItem'
import Paper from 'material-ui/Paper'
import NotificationsIcon from 'material-ui/svg-icons/social/notifications'
import EventListener, { withOptions } from 'react-event-listener'
// - Import components
import UserAvatar from 'UserAvatar'
import Notify from 'Notify'
// - Import actions
import * as globalActions from 'globalActions'
import * as authorizeActions from 'authorizeActions'
// - Create HomeHeader component class
export class HomeHeader extends Component {
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
// Default state
this.state = {
/**
* User avatar popover is open if true
*/
openAvatarMenu: false,
/**
* Show header title or not (true/false)
*/
showTitle: true,
/**
* If true notification menu will be open
*/
openNotifyMenu: false
}
// Binding functions to `this`
this.onToggleSidebar = this.onToggleSidebar.bind(this)
this.handleCloseNotify = this.handleCloseNotify.bind(this)
}
/**
* Handle close notification menu
*
*
* @memberof HomeHeader
*/
handleCloseNotify = () => {
this.setState({
openNotifyMenu: false
})
}
// On click toggle sidebar
onToggleSidebar = () => {
if (this.props.sidebarStatus) {
this.props.sidebar(false)
} else {
this.props.sidebar(true)
}
}
/**
* Handle notification touch
*
*
* @memberof HomeHeader
*/
handleNotifyTouchTap = (event) => {
// This prevents ghost click.
event.preventDefault();
this.setState({
openNotifyMenu: true,
anchorEl: event.currentTarget,
});
}
/**
* Handle touch on user avatar for popover
*
*
* @memberof HomeHeader
*/
handleAvatarTouchTap = (event) => {
// This prevents ghost click.
event.preventDefault();
this.setState({
openAvatarMenu: true,
anchorEl: event.currentTarget,
});
}
/**
* Handle logout user
*
*
* @memberof HomeHeader
*/
handleLogout = () => {
this.props.logout()
}
/**
* Handle close popover
*
*
* @memberof HomeHeader
*/
handleRequestClose = () => {
this.setState({
openAvatarMenu: false,
});
};
/**
* Handle resize event for window to manipulate home header status
* @param {event} evt is the event is passed by winodw resize event
*/
handleResize = (evt) => {
// Set initial state
var width = window.innerWidth
if (width >= 600 && !this.state.showTitle) {
this.setState({
showTitle: true
})
}
else if (width < 600 && this.state.showTitle) {
this.setState({
showTitle: false
})
}
}
componentDidMount = () => {
this.handleResize()
}
// Render app DOM component
render() {
/**
* Styles
*/
var styles = {
toolbarStyle: {
backgroundColor: "",
transition: "all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms",
boxSizing: "border-box",
fontFamily: "Roboto, sans-serif",
position: "fixed",
zIndex: "1101",
width: "100%",
top: "0px",
boxShadow: '0 1px 8px rgba(0,0,0,.3)'
},
avatarStyle: {
margin: 5,
cursor: 'pointer'
}
}
return (
<Toolbar style={styles.toolbarStyle} className="g__greenBox">
<EventListener
target="window"
onResize={this.handleResize}
onKeyUp={this.handleKeyUp}
/>
{/* Left side */}
<ToolbarGroup firstChild={true}>
<IconButton iconStyle={{ color: "#fff" }} onClick={this.onToggleSidebar} >
<SvgDehaze style={{ color: "#fff", marginLeft: "15px", cursor: "pointer" }} />
</IconButton>
{/* Header title */}
<ToolbarTitle style={{ color: "#fff", marginLeft: "15px" }} text="Green" />
{this.state.showTitle ? <div className="homeHeader__page">{this.props.title}</div> : ''}
</ToolbarGroup>
<ToolbarGroup>
</ToolbarGroup>
{/* Notification */}
<ToolbarGroup lastChild={true}>
<div className="homeHeader__right">
{this.props.notifyCount > 0 ? (<IconButton tooltip="Notifications" onTouchTap={this.handleNotifyTouchTap}>
<div className="homeHeader__notify">
<div className='title'>{this.props.notifyCount}</div>
</div>
</IconButton>)
: (<IconButton tooltip="Notifications" onTouchTap={this.handleNotifyTouchTap}>
<NotificationsIcon color='rgba(255, 255, 255, 0.87)' />
</IconButton>)}
<Notify open={this.state.openNotifyMenu} anchorEl={this.state.anchorEl} onRequestClose={this.handleCloseNotify}/>
{/* User avatar*/}
<UserAvatar
onTouchTap={this.handleAvatarTouchTap}
fileName={this.props.avatar}
size={32}
style={styles.avatarStyle}
/>
<Popover
open={this.state.openAvatarMenu}
anchorEl={this.state.anchorEl}
anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }}
targetOrigin={{ horizontal: 'left', vertical: 'top' }}
onRequestClose={this.handleRequestClose}
>
<Menu>
<MenuItem style={{ backgroundColor: 'white', color: blue500, fontSize: '14px' }} primaryText="MY ACCOUNT" />
<MenuItem primaryText="LOGOUT" style={{ fontSize: '14px' }} onClick={this.handleLogout.bind(this)} />
</Menu>
</Popover>
</div>
</ToolbarGroup>
</Toolbar>
)
}
}
// - Map dispatch to props
const mapDispatchToProps = (dispatch, ownProps) => {
return {
logout: () => dispatch(authorizeActions.dbLogout())
}
}
// - Map state to props
const mapStateToProps = (state, ownProps) => {
let notifyCount = state.notify.userNotifies
? Object
.keys(state.notify.userNotifies)
.filter((key)=> !state.notify.userNotifies[key].isSeen).length
: 0
return {
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : '',
title: state.global.headerTitle,
notifyCount
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(HomeHeader)

View File

@@ -0,0 +1,246 @@
// - Impoer react components
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { GridList, GridTile } from 'material-ui/GridList'
import IconButton from 'material-ui/IconButton'
import Subheader from 'material-ui/Subheader'
import StarBorder from 'material-ui/svg-icons/toggle/star-border'
import FloatingActionButton from 'material-ui/FloatingActionButton';
import SvgUpload from 'material-ui/svg-icons/file/cloud-upload'
import SvgAddImage from 'material-ui/svg-icons/image/add-a-photo'
import SvgDelete from 'material-ui/svg-icons/action/delete'
import { grey200, grey600 } from 'material-ui/styles/colors'
import FlatButton from 'material-ui/FlatButton'
import uuid from 'uuid'
// - Import actions
import * as imageGalleryActions from 'imageGalleryActions'
import * as fileActions from 'fileActions'
import * as globalActions from 'globalActions'
// - Import app components
import Img from 'Img'
// - Import API
import FileAPI from 'FileAPI'
/**
* Create ImageGallery component class
*/
export class ImageGallery extends Component {
static propTypes = {
/**
* Callback function to ser image url on parent component
*/
open: PropTypes.func
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props);
// Binding function to `this`
this.close = this.close.bind(this)
this.onFileChange = this.onFileChange.bind(this)
this.handleSetImage = this.handleSetImage.bind(this)
this.handleDeleteImage = this.handleDeleteImage.bind(this)
this.imageList = this.imageList.bind(this)
}
/**
* Handle set image
* @param {event} evt passed by on click event on add image
* @param {string} name is the name of the image
*/
handleSetImage = (evt, name) => {
this.props.set(name)
this.close()
}
/**
* Handle delete image
* @param {event} evt passed by on click event on delete image
* @param {integer} id is the image identifier which selected to delete
*/
handleDeleteImage = (evt, id) => {
this.props.deleteImage(id)
}
componentDidMount() {
window.addEventListener("onSendResizedImage", this.handleSendResizedImage)
}
componentWillUnmount() {
window.removeEventListener("onSendResizedImage", this.handleSendResizedImage)
}
/**
* Handle send image resize event that pass the resized image
*
*
* @memberof ImageGallery
*/
handleSendResizedImage = (event) => {
const { resizedImage, fileName } = event.detail
this.props.saveImage(resizedImage, fileName)
}
/**
* Handle on change file upload
*/
onFileChange = (evt) => {
const extension = FileAPI.getExtension(evt.target.files[0].name)
var fileName = (`${uuid()}.${extension}`)
let image = FileAPI.constraintImage(evt.target.files[0], fileName)
}
/**
* Hide image gallery
*/
close = () => {
this.props.close()
}
imageList = () => {
console.log('====================================');
console.log(this.props.images);
console.log('====================================');
return this.props.images.map((image, index) => {
return (<GridTile
key={image.id}
title={<SvgDelete hoverColor={grey200} color="white" color="white" style={{ marginLeft: "5px", cursor: "pointer" }} onClick={evt => this.handleDeleteImage(evt, image.id)} />}
subtitle={<span></span>}
actionIcon={<SvgAddImage hoverColor={grey200} color="white" style={{ marginRight: "5px", cursor: "pointer" }} onClick={evt => this.handleSetImage(evt, image.name)} />}
>
<div>
<div style={{ overflowY: "hidden", overflowX: "auto" }}>
<ul style={{ whiteSpace: "nowrap", padding: "0 6px", margin: "8px 0 0 0", verticalAlign: "bottom", flexShrink: 0, listStyleType: "none" }}>
<div style={{ display: "block" }}>
<div style={{ display: "block", marginRight: "8px", transition: "transform .25s" }}>
<li style={{ width: "100%", margin: 0, verticalAlign: "bottom", position: "static", display: "inline-block" }}>
<Img fileName={image.name} style={{ width: "100%", height: "auto" }} />
</li>
</div>
</div>
</ul>
</div>
</div>
</GridTile>)
})
}
/**
* When the post text changed
* @param {event} evt is an event passed by change post text callback funciton
* @param {string} data is the post content which user writes
*/
render() {
/**
* Component styles
* @type {Object}
*/
const styles = {
root: {
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-around',
},
gridList: {
width: 500,
height: 450,
overflowY: 'auto',
},
uploadButton: {
verticalAlign: 'middle',
},
uploadInput: {
cursor: 'pointer',
position: 'absolute',
top: 0,
bottom: 0,
right: 0,
left: 0,
width: '100%',
opacity: 0,
}
}
return (
<div style={styles.root}>
<GridList
cellHeight={180}
style={styles.gridList}
>
<GridTile >
<div style={{ display: "flex", backgroundColor: "rgba(222, 222, 222, 0.52)", flexDirection: "column", justifyContent: "center", alignItems: "center", height: "100%" }}>
<FlatButton
label="Upload Photo"
labelStyle={{ fontWeight: 100 }}
labelPosition="before"
style={styles.uploadButton}
containerElement="label"
>
<input type="file" onChange={this.onFileChange} accept="image/*" style={styles.uploadInput} />
</FlatButton>
</div>
</GridTile>
{this.imageList()}
</GridList>
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
saveImage: (file, fileName) => {
dispatch(imageGalleryActions.dbUploadImage(file, fileName))
},
deleteImage: (id) => {
dispatch(imageGalleryActions.dbDeleteImage(id))
}
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state) => {
return {
images: state.imageGallery.images,
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : ''
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(ImageGallery)

158
app/components/Img.jsx Normal file
View File

@@ -0,0 +1,158 @@
// - Import react components
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import SvgImage from 'material-ui/svg-icons/image/image'
// - Import app components
// - Import API
// - Import actions
import * as imageGalleryActions from 'imageGalleryActions'
/**
* Create component class
*/
export class Img extends Component {
static propTypes = {
/**
* Use for getting url address from server
*/
fileName: PropTypes.string,
/**
* Avatar style
*/
style: PropTypes.object
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
//Defaul state
this.state = {
isImageLoaded: false
}
// Binding functions to `this`
this.getImageURL = this.getImageURL.bind(this)
this.handleLoadImage = this.handleLoadImage.bind(this)
}
/**
* Will be called on loading image
*
* @memberof Img
*/
handleLoadImage = () => {
this.setState({
isImageLoaded: true
})
}
/**
* Get image url if it is not exist on redux store
*
* @memberof Img
*/
getImageURL = () => {
let { fileName } = this.props
if (fileName && fileName !== '') {
if (this.props.imageRequests.indexOf(fileName) > -1)
return
this.props.getImage(fileName)
}
}
componentWillMount() {
let { fileName } = this.props
if (this.props.imageRequests.indexOf(fileName) > -1)
return
this.getImageURL()
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
const styles = {
loding: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: '100px',
position: 'relative',
color: '#cacecd',
fontWeight: 100
},
loadingContent: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
},
loadingImage: {
fill: 'aliceblue',
width: '50px',
height: '50px'
}
}
let { fileName, style } = this.props
let { isImageLoaded } = this.state
return (
<div>
<img onLoad={this.handleLoadImage} src={this.props.avatarURL[fileName] || ''} style={isImageLoaded ? style : { display: 'none' }} />
<div style={{ backgroundColor: 'blue' }} style={isImageLoaded ? { display: 'none' } : styles.loding}>
<div style={styles.loadingContent}>
<SvgImage style={styles.loadingImage} />
<div>Image has not loaded</div>
</div>
</div>
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
getImage: (name) => dispatch(imageGalleryActions.dbDownloadImage(name))
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
avatarURL: state.imageGallery.imageURLList,
imageRequests: state.imageGallery.imageRequests
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(Img)

191
app/components/ImgCover.jsx Normal file
View File

@@ -0,0 +1,191 @@
// - Import react components
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import SvgImage from 'material-ui/svg-icons/image/image'
// - Import app components
// - Import API
// - Import actions
import * as imageGalleryActions from 'imageGalleryActions'
/**
* Create component class
*/
export class ImgCover extends Component {
static propTypes = {
/**
* Use for getting url address from server
*/
fileName: PropTypes.string,
/**
* Image width
*/
width: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]).isRequired,
/**
* Image height
*/
height: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]).isRequired,
/**
* Image border radius
*/
borderRadius: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
])
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
//Defaul state
this.state = {
isImageLoaded: false
}
// Binding functions to `this`
this.getImageURL = this.getImageURL.bind(this)
this.handleLoadImage = this.handleLoadImage.bind(this)
}
/**
* Will be called on loading image
*
* @memberof Img
*/
handleLoadImage = () => {
this.setState({
isImageLoaded: true
})
}
/**
* Get image url if it is not exist on redux store
*
* @memberof Img
*/
getImageURL = () => {
let { fileName } = this.props
if (fileName && fileName !== '') {
if (this.props.imageRequests.indexOf(fileName) > -1)
return
this.props.getImage(fileName)
}
}
componentWillMount() {
let { fileName } = this.props
if (this.props.imageRequests.indexOf(fileName) > -1)
return
this.getImageURL()
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
let { fileName, style } = this.props
let { isImageLoaded } = this.state
/**
* Styles
*/
const styles = {
cover: {
backgroundImage: 'url(' + (this.props.avatarURL[fileName] || '#') + ')',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center',
width: this.props.width,
height: this.props.height,
borderRadius: this.props.borderRadius
},
loding: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: '100px',
position: 'relative',
color: '#cacecd',
fontWeight: 100
},
loadingContent: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
},
loadingImage: {
fill: 'aliceblue',
width: '50px',
height: '50px'
}
}
return (
<div>
<div style={Object.assign({},styles.cover,style)}>
{this.props.children}
</div>
<div style={{ backgroundColor: 'blue' }} style={isImageLoaded ? { display: 'none' } : styles.loding}>
<div style={styles.loadingContent}>
<SvgImage style={styles.loadingImage} />
<div>Image has not loaded</div>
</div>
</div>
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
getImage: (name) => dispatch(imageGalleryActions.dbDownloadImage(name))
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
avatarURL: state.imageGallery.imageURLList,
imageRequests: state.imageGallery.imageRequests
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(ImgCover)

212
app/components/Login.jsx Normal file
View File

@@ -0,0 +1,212 @@
// - Import external components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { NavLink, withRouter } from 'react-router-dom'
import { push } from 'react-router-redux'
import Paper from 'material-ui/Paper'
import TextField from 'material-ui/TextField'
import RaisedButton from 'material-ui/RaisedButton'
import FlatButton from 'material-ui/FlatButton'
// - Import actions
import * as authorizeActions from 'authorizeActions'
// - Create Login component class
export class Login extends Component {
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props);
this.state = {
emailInput: '',
emailInputError: '',
passwordInput: '',
passwordInputError: '',
}
// Binding function to `this`
this.handleForm = this.handleForm.bind(this)
}
/**
* Handle data on input change
* @param {event} evt is an event of inputs of element on change
*/
handleInputChange = (evt) => {
const target = evt.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
switch (name) {
case 'emailInput':
this.setState({
emailInputError: ''
})
break
case 'passwordInput':
this.setState({
confirmInputError: '',
passwordInputError: ''
})
break;
default:
}
}
/**
* Handle register form
*/
handleForm = () => {
var error = false
if (this.state.emailInput === '') {
this.setState({
emailInputError: 'This field is required'
})
error = true
}
if (this.state.passwordInput === '') {
this.setState({
passwordInputError: 'This field is required'
})
error = true
}
if (!error) {
this.props.login(
this.state.emailInput,
this.state.passwordInput
)
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
const paperStyle = {
minHeight: 370,
width: 450,
margin: 20,
textAlign: 'center',
display: 'block',
margin: "auto"
};
return (
<form>
<h1 style={{
textAlign: "center",
padding: "20px",
fontSize: "30px",
fontWeight: 500,
lineHeight: "32px",
margin: "auto",
color: "rgba(138, 148, 138, 0.2)"
}}>Green</h1>
<div className="animate-bottom">
<Paper style={paperStyle} zDepth={1} rounded={false} >
<div style={{ padding: "48px 40px 36px" }}>
<div style={{
paddingLeft: "40px",
paddingRight: "40px"
}}>
<h2 style={{
textAlign: "left",
paddingTop: "16px",
fontSize: "24px",
fontWeight: 400,
lineHeight: "32px",
margin: 0
}}>Sign in</h2>
</div>
<TextField
onChange={this.handleInputChange}
errorText={this.state.emailInputError}
name="emailInput"
floatingLabelStyle={{ fontSize: "15px" }}
floatingLabelText="Email"
type="email"
tabIndex={1}
/><br />
<TextField
onChange={this.handleInputChange}
errorText={this.state.passwordInputError}
name="passwordInput"
floatingLabelStyle={{ fontSize: "15px" }}
floatingLabelText="Password"
type="password"
tabIndex={2}
/><br />
<br />
<br />
<div className="login__button-box">
<div>
<FlatButton label="Create an account" onClick={this.props.signupPage} tabIndex={4} />
</div>
<div >
<RaisedButton label="Login" primary={true} onClick={this.handleForm} tabIndex={3} />
</div>
</div>
</div>
</Paper>
</div>
</form>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
login: (username, password) => {
dispatch(authorizeActions.dbLogin(username, password))
},
signupPage: () => {
dispatch(push("/signup"))
}
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
}
}
// - Connect component to redux store
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Login))

216
app/components/Master.jsx Normal file
View File

@@ -0,0 +1,216 @@
// - Import react components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Route, Switch, NavLink, withRouter, Redirect } from 'react-router-dom'
import { firebaseAuth, firebaseRef } from 'app/firebase/'
import { push } from 'react-router-redux'
import Snackbar from 'material-ui/Snackbar';
import LinearProgress from 'material-ui/LinearProgress'
// - Import components
import Home from 'Home'
import Signup from 'Signup'
import Login from 'Login'
import Settings from 'Settings'
import MasterLoading from 'MasterLoading'
// - Import API
import { PrivateRoute, PublicRoute } from 'AuthRouterAPI'
// - Import actions
import * as authorizeActions from 'authorizeActions'
import * as imageGalleryActions from 'imageGalleryActions'
import * as postActions from 'postActions'
import * as commentActions from 'commentActions'
import * as voteActions from 'voteActions'
import * as userActions from 'userActions'
import * as globalActions from 'globalActions'
import * as circleActions from 'circleActions'
import * as notifyActions from 'notifyActions'
/* ------------------------------------ */
// - Create Master component class
export class Master extends Component {
static isPrivate = true
// Constructor
constructor(props) {
super(props);
this.state = {
loading: true,
authed: false,
dataLoaded: false
};
// Binding functions to `this`
this.handleLoading = this.handleLoading.bind(this)
this.handleMessage = this.handleMessage.bind(this)
}
// Handle click on message
handleMessage = (evt) => {
this.props.closeMessage()
}
// Handle loading
handleLoading = (status) => {
this.setState({
loading: status,
authed: false
})
}
componentWillMount = () => {
firebaseAuth().onAuthStateChanged((user) => {
if (user) {
this.props.login(user)
this.setState({
loading: false
})
if (!this.props.global.defaultLoadDataStatus) {
this.props.clearData()
this.props.loadData()
this.props.defaultDataEnable()
}
} else {
this.props.logout()
this.setState({
loading: false
})
if (this.props.global.defaultLoadDataStatus) {
this.props.defaultDataDisable()
this.props.clearData()
}
this.props.loadDataGuest()
}
})
}
/**
* Render app DOM component
*
* @returns
*
* @memberof Master
*/
render() {
const {progress, global} = this.props
return (
<div id="master">
<div className='master__progress' style={{display: (progress.visible ? 'block' : 'none' )}}>
<LinearProgress mode="determinate" value={progress.percent} />
</div>
<div className='master__loading animate-fading2' style={{display: ( global.showTopLoading ? 'flex' : 'none' )}}>
<div className='title'> Loading ... </div>
</div>
<MasterLoading activeLoading={this.state.loading || !(this.props.loaded || this.props.guest)} handleLoading={this.handleLoading} />
{(!this.state.loading && (this.props.loaded || this.props.guest))
?(<Switch>
<Route path="/signup" component={Signup} />
<Route path="/settings" component={Settings} />
<Route path="/login" render={() => {
console.log('this.props.authed: ', this.props.authed, "this.props: ", this.props)
return (
this.props.authed
? <Redirect to="/" />
: <Login />
)
}
} />
<Route render={() =><Home uid={this.props.uid}/>} />
</Switch>) : ''
}
<Snackbar
open={this.props.global.messageOpen}
message={this.props.global.message}
autoHideDuration={4000}
style={{left: '1%', transform: 'none'}}
/>
</div>
)
}
}
// - Map dispatch to props
const mapDispatchToProps = (dispatch, ownProps) => {
return {
loadData: () => {
dispatch(commentActions.dbGetComments())
dispatch(imageGalleryActions.downloadForImageGallery())
dispatch(postActions.dbGetPosts())
dispatch(userActions.dbGetUserInfo())
dispatch(voteActions.dbGetVotes())
dispatch(notifyActions.dbGetNotifies())
dispatch(circleActions.dbGetCircles())
},
clearData: () => {
dispatch(imageGalleryActions.clearAllData())
dispatch(postActions.clearAllData())
dispatch(userActions.clearAllData())
dispatch(commentActions.clearAllData())
dispatch(voteActions.clearAllvotes())
dispatch(notifyActions.clearAllNotifications())
dispatch(circleActions.clearAllCircles())
},
login: (user) => {
dispatch(authorizeActions.login(user.uid))
},
logout: () => {
dispatch(authorizeActions.logout())
},
defaultDataDisable: () => {
dispatch(globalActions.defaultDataDisable())
},
defaultDataEnable: () => {
dispatch(globalActions.defaultDataEnable())
},
closeMessage: () => {
dispatch(globalActions.hideMessage())
},
loadDataGuest: () => {
dispatch(globalActions.loadDataGuest())
}
}
}
// - Map state to props
const mapStateToProps = ({authorize, global, user, post, comment, imageGallery , vote, notify,circle }) => {
return {
guest: authorize.guest,
uid: authorize.uid,
authed: authorize.authed,
progress: global.progress,
global: global,
loaded: user.loaded && post.loaded && comment.loaded && imageGallery.loaded && vote.loaded && notify.loaded && circle.loaded
}
}
// - Connect commponent to redux store
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Master))

View File

@@ -0,0 +1,53 @@
// - Import react components
import React, {Component} from 'react'
import CircularProgress from 'material-ui/CircularProgress'
import Dialog from 'material-ui/Dialog'
// - Import app components
// - Create MasterLoading component class
export default class MasterLoading extends Component {
// Constructor
constructor(props) {
super(props);
// Binding functions to `this`
}
// Render app DOM component
render() {
return (
<Dialog
modal={true}
open={this.props.activeLoading}
autoDetectWindowHeight={false}
overlayStyle={{backgroundColor: "white"}}
contentClassName="mLoading__content"
bodyStyle={{backgroundColor: ""}}
bodyClassName="mLoading__body"
>
<div>
<div className="mLoading__context">
<CircularProgress color="white" size={80} thickness={7} />
<h1 style={{float:"right", color:"#fff"}}>Green</h1>
</div>
</div>
</Dialog>
);
}
}

139
app/components/Notify.jsx Normal file
View File

@@ -0,0 +1,139 @@
// - Import react components
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import Popover, { PopoverAnimationVertical } from 'material-ui/Popover'
// - Import app components
import NotifyItem from 'NotifyItem'
// - Import API
// - Import actions
import * as userActions from 'userActions'
/**
* Create component class
*/
export class Notify extends Component {
static propTypes = {
/**
* It will be true if the notification is open
*/
open: PropTypes.bool,
/**
* Pass anchor element
*/
anchorEl: PropTypes.any,
/**
* Fire to close notificaion
*/
onRequestClose: PropTypes.func,
/**
* If user's seen notification box or not (true/false)
*/
isSeen: PropTypes.bool
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
//Defaul state
this.state = {
}
// Binding functions to `this`
}
notifyItemList = () => {
let { notifications, info, onRequestClose } = this.props
let parsedDOM = []
if (notifications) {
Object.keys(notifications).forEach((key) => {
const {notifierUserId} = notifications[key]
parsedDOM.push(
<NotifyItem
key={key}
description={(notifications[key] ? notifications[key].description || '' : '')}
fullName={(info[notifierUserId] ? info[notifierUserId].fullName || '' : '')}
avatar={(info[notifierUserId] ? info[notifierUserId].avatar || '' : '')}
id={key}
isSeen={(notifications[key] ? notifications[key].isSeen || false : false )}
url={(notifications[key] ? notifications[key].url || '' : '')}
notifierUserId={notifierUserId}
closeNotify={onRequestClose}
/>
)
})
}
return parsedDOM
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
let { open, anchorEl, onRequestClose } = this.props
return (
<Popover
className='homeHeader__notify-menu'
open={open}
anchorEl={anchorEl}
anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }}
targetOrigin={{ horizontal: 'left', vertical: 'top' }}
onRequestClose={onRequestClose}
>
<div className='container'>
<div className='title'>Green </div>
<div className='content'>
{this.notifyItemList()}
</div>
</div>
</Popover>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
notifications: state.notify.userNotifies,
info: state.user.info
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(Notify)

View File

@@ -0,0 +1,155 @@
// - Import react components
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { NavLink } from 'react-router-dom'
import { push } from 'react-router-redux'
import SvgClose from 'material-ui/svg-icons/navigation/close'
import { grey400 } from 'material-ui/styles/colors'
// - Import app components
import UserAvatar from 'UserAvatar'
// - Import API
// - Import actions
import * as notifyActions from 'notifyActions'
/**
* Create component class
*/
export class NotifyItem extends Component {
static propTypes = {
/**
* Notification description
*/
description: PropTypes.string,
/**
* Which user relates to the notification item
*/
fullName: PropTypes.string,
/**
* Avatar of the user who relate to the notification item
*/
avatar: PropTypes.string,
/**
* Notification identifier
*/
id: PropTypes.string,
/**
* If user's seen the notification or not (true/false)
*/
isSeen: PropTypes.bool,
/**
* Which address notification refers
*/
url: PropTypes.string,
/**
* The notifier user identifier
*/
notifierUserId: PropTypes.string,
/**
* Close notification popover
*/
closeNotify: PropTypes.func
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
//Defaul state
this.state = {
}
// Binding functions to `this`
this.handleSeenNotify = this.handleSeenNotify.bind(this)
}
handleSeenNotify = (evt) => {
evt.preventDefault()
const { seenNotify, id, url, goTo, isSeen, closeNotify } = this.props
if (id) {
if (!isSeen)
seenNotify(id)
closeNotify()
goTo(url)
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
let { description, fullName, avatar, isSeen, id, goTo,closeNotify, notifierUserId, url, deleteNotiy } = this.props
return (
<div className='item' style={isSeen ? { opacity: 0.6 } : {}} key={id}>
<div className='avatar'>
<NavLink
to={`/${notifierUserId}`}
onClick={(evt) => {
evt.preventDefault()
closeNotify()
goTo(`/${notifierUserId}`)
}}
>
<UserAvatar fileName={avatar} />
</NavLink>
</div>
<div className='info'>
<NavLink to={url} onClick={this.handleSeenNotify}>
<div className='user-name'>
{fullName}
</div>
<div className='description'>
{description}
</div>
</NavLink>
</div>
<div className='close' onClick={() => deleteNotiy(id)}>
<SvgClose hoverColor={grey400} style={{ cursor: 'pointer' }} />
</div>
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
goTo: (url) => dispatch(push(url)),
seenNotify: (id) => dispatch(notifyActions.dbSeenNotify(id)),
deleteNotiy: (id) => dispatch(notifyActions.dbDeleteNotify(id))
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(NotifyItem)

174
app/components/People.jsx Normal file
View File

@@ -0,0 +1,174 @@
// - Import react components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import {withRouter} from 'react-router-dom'
import PropTypes from 'prop-types'
import { Tabs, Tab } from 'material-ui/Tabs'
import { grey50, grey200, grey400, grey600, cyan500 } from 'material-ui/styles/colors'
import {push} from 'react-router-redux'
// - Import app components
import FindPeople from 'FindPeople'
import Following from 'Following'
import Followers from 'Followers'
import YourCircles from 'YourCircles'
// - Import API
// - Import actions
import * as circleActions from 'circleActions'
import * as globalActions from 'globalActions'
/**
* Create component class
*/
export class People extends Component {
static propTypes = {
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
//Defaul state
this.state = {
}
// Binding functions to `this`
}
componentWillMount() {
const {tab} = this.props.match.params
switch (tab) {
case undefined:
case '':
this.props.setHeaderTitle('People')
break;
case 'circles':
this.props.setHeaderTitle('Circles')
break;
case 'followers':
this.props.setHeaderTitle('Followers')
break;
default:
break;
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
/**
* Component styles
*/
const styles = {
people: {
margin: '0 auto',
width: '90%'
},
headline: {
fontSize: 24,
paddingTop: 16,
marginBottom: 12,
fontWeight: 400,
},
slide: {
padding: 10,
}
}
const {circlesLoaded} = this.props
const {tab} = this.props.match.params
let tabIndex = 0
switch (tab) {
case undefined:
case '':
tabIndex = 0
break;
case 'circles':
tabIndex = 1
break;
case 'followers':
tabIndex = 2
break;
default:
break;
}
return (
<div style={styles.people}>
<Tabs inkBarStyle={{backgroundColor: grey50}} initialSelectedIndex={tabIndex} >
<Tab label="Find People" onActive={() => {
this.props.goTo('/people')
this.props.setHeaderTitle('People')
}} >
{circlesLoaded ? <FindPeople /> : ''}
</Tab>
<Tab label="Following" onActive={() => {
this.props.goTo('/people/circles')
this.props.setHeaderTitle('Circles')
}} >
{circlesLoaded ? <Following/> : ''}
{circlesLoaded ? <YourCircles /> : ''}
</Tab>
<Tab label="Followers" onActive={() => {
this.props.goTo('/people/followers')
this.props.setHeaderTitle('Followers')
}}>
{circlesLoaded ? <Followers /> : ''}
</Tab>
</Tabs>
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
goTo: (url)=> dispatch(push(url)),
setHeaderTitle : (title) => dispatch(globalActions.setHeaderTitle(title))
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
uid: state.authorize.uid,
circlesLoaded: state.circle.loaded
}
}
// - Connect component to redux store
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(People))

495
app/components/Post.jsx Normal file
View File

@@ -0,0 +1,495 @@
// - Import react components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import {NavLink} from 'react-router-dom'
import { push } from 'react-router-redux'
import PropTypes from 'prop-types'
import moment from 'moment'
import { createAction as action } from 'redux-actions'
import { Card, CardActions, CardHeader, CardMedia, CardTitle, CardText } from 'material-ui/Card'
import FloatingActionButton from 'material-ui/FloatingActionButton'
import SvgShare from 'material-ui/svg-icons/social/share'
import SvgLink from 'material-ui/svg-icons/content/link'
import SvgComment from 'material-ui/svg-icons/Communication/comment'
import SvgFavorite from 'material-ui/svg-icons/action/favorite'
import SvgFavoriteBorder from 'material-ui/svg-icons/action/favorite-border'
import Checkbox from 'material-ui/Checkbox'
import FlatButton from 'material-ui/FlatButton'
import Divider from 'material-ui/Divider'
import { grey200, grey400, grey600 } from 'material-ui/styles/colors'
import Paper from 'material-ui/Paper'
import Menu from 'material-ui/Menu'
import MenuItem from 'material-ui/MenuItem'
import TextField from 'material-ui/TextField'
import Dialog from 'material-ui/Dialog'
import IconButton from 'material-ui/IconButton'
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'
import IconMenu from 'material-ui/IconMenu'
import reactStringReplace from 'react-string-replace'
// - Import app components
import CommentGroup from 'CommentGroup'
import PostWrite from 'PostWrite'
import Img from 'Img'
import IconButtonElement from 'IconButtonElement'
import UserAvatar from 'UserAvatar'
// - Import actions
import * as voteActions from 'voteActions'
import * as postActions from 'postActions'
import * as globalActions from 'globalActions'
// - Create component class
export class Post extends Component {
static propTypes = {
/**
* The context of a post
*/
body: PropTypes.string,
/**
* The number of comment on a post
*/
commentCounter:PropTypes.number,
/**
* Creation post date
*/
creationDate:PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
/**
* Post identifier
*/
id:PropTypes.string,
/**
* Post image address
*/
image:PropTypes.string,
/**
* The last time date when post has was edited
*/
lastEditDate:PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
/**
* The name of the user who created the post
*/
ownerDisplayName:PropTypes.string,
/**
* The identifier of the user who created the post
*/
ownerUserId:PropTypes.string,
/**
* The avatar address of the user who created the post
*/
ownerAvatar:PropTypes.string,
/**
* If post is only [0]text, [1]whith picture, ...
*/
postTypeId:PropTypes.number,
/**
* The number votes on a post
*/
score:PropTypes.number,
/**
* Array of tags on a post
*/
tags:PropTypes.array,
/**
* The video address of a post
*/
video:PropTypes.string,
/**
* If it's true comment will be disabled on a post
*/
disableComments:PropTypes.bool,
/**
* If it's true sharing will be disabled on a post
*/
disableSharing:PropTypes.bool,
/**
* The number of users who has visited the post
*/
viewCount:PropTypes.number
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
this.state = {
/**
* Post text
*/
text: this.props.body,
/**
* It's true if whole the text post is visible
*/
readMoreState: false,
/**
* Handle open comment from parent component
*/
openComments: false,
/**
* If it's true, share dialog will be open
*/
shareOpen: false,
/**
* If it's true comment will be disabled on post
*/
disableComments: this.props.disableComments,
/**
* If it's true share will be disabled on post
*/
disableSharing: this.props.disableSharing,
/**
* Title of share post
*/
shareTitle: 'Share On',
/**
* If it's true, post link will be visible in share post dialog
*/
openCopyLink: false,
/**
* If it's true, post write will be open
*/
openPostWrite: false
}
// Binding functions to this
this.handleReadMore = this.handleReadMore.bind(this)
this.getOpenCommentGroup = this.getOpenCommentGroup.bind(this)
this.handleVote = this.handleVote.bind(this)
this.handleOpenShare = this.handleOpenShare.bind(this)
this.handleCloseShare = this.handleCloseShare.bind(this)
this.handleCopyLink = this.handleCopyLink.bind(this)
this.handleDelete = this.handleDelete.bind(this)
this.handleOpenPostWrite = this.handleOpenPostWrite.bind(this)
this.handleClosePostWrite = this.handleClosePostWrite.bind(this)
this.handleOpenComments = this.handleOpenComments.bind(this)
}
/**
* Toggle on show/hide comment
* @param {event} evt passed by clicking on comment slide show
*/
handleOpenComments = (evt) => {
this.setState({
openComments: !this.state.openComments
})
}
/**
* Open post write
*
*
* @memberof Blog
*/
handleOpenPostWrite = () => {
this.setState({
openPostWrite: true
})
}
/**
* Close post write
*
*
* @memberof Blog
*/
handleClosePostWrite = () => {
this.setState({
openPostWrite: false
})
}
/**
* Delete a post
*
*
* @memberof Post
*/
handleDelete = () => {
this.props.delete(this.props.id)
}
/**
* Show copy link
*
*
* @memberof Post
*/
handleCopyLink = () => {
this.setState({
openCopyLink: true,
shareTitle: 'Copy Link'
})
}
/**
* Open share post
*
*
* @memberof Post
*/
handleOpenShare = () => {
this.setState({
shareOpen: true
})
}
/**
* Close share post
*
*
* @memberof Post
*/
handleCloseShare = () => {
this.setState({
shareOpen: false,
shareTitle: 'Share On',
openCopyLink: false
})
}
/**
* Handle vote on a post
*
*
* @memberof Post
*/
handleVote = () => {
if (this.props.userVoteStatus) {
this.props.unvote()
}
else {
this.props.vote()
}
}
/**
* Set open comment group function on state which passed by CommentGroup component
* @param {function} open the function to open comment list
*/
getOpenCommentGroup = (open) => {
this.setState({
openCommentGroup: open
})
}
/**
* Handle read more event
* @param {event} evt is the event passed by click on read more
*/
handleReadMore(evt) {
this.setState({
readMoreState: !this.state.readMoreState
});
}
componentDidMount() {
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
/**
* DOM styles
*
*
* @memberof Post
*/
const styles = {
counter: {
lineHeight: '36px',
color: '#777',
fontSize: '12px',
marginRight: '6px'
},
dialog: {
width: '',
maxWidth: '530px',
borderRadius: "4px"
},
rightIconMenu: {
position: 'absolute',
right: 18,
top: 8,
},
iconButton: {
width: 24,
height: 24
}
}
const RightIconMenu = () => (
<IconMenu iconButtonElement={IconButtonElement} style={{ display: "block", position: "absolute", top: "0px", right: "4px" }}>
<MenuItem primaryText="Edit" onClick={this.handleOpenPostWrite} />
<MenuItem primaryText="Delete" onClick={this.handleDelete} />
<MenuItem primaryText={this.props.disableComments ? "Enable comments" : "Disable comments"} onClick={() => this.props.toggleDisableComments(!this.props.disableComments)} />
<MenuItem primaryText={this.props.disableSharing ? "Enable sharing" : "Disable sharing"} onClick={() => this.props.toggleSharingComments(!this.props.disableSharing)} />
</IconMenu>
)
const {ownerUserId,setHomeTitle, goTo, ownerDisplayName,creationDate, avatar, isPostOwner,image, body} = this.props
// Define variables
return (
<Card>
<CardHeader
title={<NavLink to={`/${ownerUserId}`}>{ownerDisplayName}</NavLink>}
subtitle={moment.unix(creationDate).fromNow() + " | public"}
avatar={<NavLink to={`/${ownerUserId}`}><UserAvatar fileName={avatar} size={36} /></NavLink>}
>
{isPostOwner ? ( <div style={styles.rightIconMenu}><RightIconMenu /></div>) : ''}
</CardHeader>
{image ? (
<CardMedia>
<Img fileName={image} />
</CardMedia>) : ''}
<CardText>
{reactStringReplace(body,/#(\w+)/g, (match, i) => (
<NavLink
style={{color:'green'}}
key={match + i}
to={`/tag/${match}`}
onClick ={evt => {
evt.preventDefault()
goTo(`/tag/${match}`)
setHomeTitle(`#${match}`)
}}
>
#{match}
</NavLink>
))}
</CardText>
<CardActions>
<div style={{ margin: "16px 8px", display: 'flex', justifyContent: 'space-between' }}>
<div style={{ display: 'flex' }}>
{/*<FloatingActionButton style={{ margin: "0 8px" }} zDepth={1} backgroundColor={grey200} iconStyle={{ color: grey600, fill: grey600, height: "36px", width: "36px" }} zDepth={1} secondary={false}>*/}
<div className='g__circle' onClick={this.handleVote}>
<Checkbox
checkedIcon={<SvgFavorite style={{fill:'#4CAF50'}}/>}
uncheckedIcon={<SvgFavoriteBorder style={{fill:'#757575'}} />}
defaultChecked={this.props.userVoteStatus}
style={{transform: 'translate(6px, 6px)'}}
/>
</div>
<div style={styles.counter}> {this.props.voteCount > 0 ? this.props.voteCount : ''} </div>
</div>
<div style={{ display: 'flex' }}>
{!this.props.disableComments ? (<div style={{display: 'inherit'}}><FloatingActionButton onClick={this.handleOpenComments} style={{ margin: "0 8px" }} zDepth={1} backgroundColor={grey200} iconStyle={{ color: grey600, fill: grey600, height: "36px", width: "36px" }} zDepth={1} secondary={false}>
<SvgComment viewBox="0 -9 24 34" style={{ height: "30px", width: "30px" }} /> 3
</FloatingActionButton>
<div style={styles.counter}>{this.props.commentCount > 0 ? this.props.commentCount : ''} </div></div>) : ''}
{!this.props.disableSharing ? (<FloatingActionButton onClick={this.handleOpenShare} style={{ margin: "0 8px" }} zDepth={1} backgroundColor={grey200} iconStyle={{ color: grey600, fill: grey600, height: "36px", width: "36px" }} zDepth={1} secondary={false}>
<SvgShare viewBox="0 -9 24 34" style={{ height: "30px", width: "30px" }} />
</FloatingActionButton>) : ''}
</div>
</div>
</CardActions>
<CommentGroup open={this.state.openComments} ownerPostUserId={this.props.ownerUserId} onToggleRequest={this.handleOpenComments} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments} postId={this.props.id} />
{/* Copy link dialog*/}
<Dialog
title="Share On"
modal={false}
open={this.state.shareOpen}
onRequestClose={this.handleCloseShare}
overlayStyle={{ background: "rgba(0,0,0,0.12)" }}
contentStyle={styles.dialog}
autoDetectWindowHeight={false}
actionsContainerStyle={{ borderTop: "1px solid rgb(224, 224, 224)" }}
>
{!this.state.openCopyLink
? (<Paper >
<Menu>
<MenuItem primaryText="Copy Link" leftIcon={<SvgLink />} onClick={this.handleCopyLink} />
</Menu>
</Paper>)
: <TextField fullWidth={true} id="text-field-default" defaultValue={`${location.origin}/${this.props.ownerUserId}/posts/${this.props.id}`} />
}
</Dialog>
<PostWrite
open={this.state.openPostWrite}
onRequestClose={this.handleClosePostWrite}
edit={true}
text= {this.props.body}
image= {this.props.image ? this.props.image : ''}
id= {this.props.id}
disableComments= {this.props.disableComments}
disableSharing= {this.props.disableSharing}
/>
</Card>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
vote: () => dispatch(voteActions.dbAddVote(ownProps.id,ownProps.ownerUserId)),
unvote: () => dispatch(voteActions.dbDeleteVote(ownProps.id)) ,
delete: (id) => dispatch(postActions.dbDeletePost(id)),
toggleDisableComments: (status) => dispatch(postActions.dbUpdatePost({id:ownProps.id, disableComments:status},_ =>_)),
toggleSharingComments: (status) => dispatch(postActions.dbUpdatePost({id:ownProps.id, disableSharing:status},_ => _)),
goTo: (url) => dispatch(push(url)),
setHomeTitle: (title) => dispatch(globalActions.setHeaderTitle(title|| ''))
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
const {uid} = state.authorize
let votes = state.vote.postVotes[ownProps.id]
const post = (state.post.userPosts[uid] ? Object.keys(state.post.userPosts[uid]).filter((key)=>{ return ownProps.id === key}).length : 0)
return {
avatar: state.user.info && state.user.info[ownProps.ownerUserId] ? state.user.info[ownProps.ownerUserId].avatar || '' : '',
commentCount: state.comment.postComments[ownProps.id] ? Object.keys(state.comment.postComments[ownProps.id]).length : 0,
voteCount: state.vote.postVotes[ownProps.id] ? Object.keys(state.vote.postVotes[ownProps.id]).length : 0,
userVoteStatus: votes && Object.keys(votes).filter((key) => votes[key].userId === state.authorize.uid)[0] ? true : false,
isPostOwner: post > 0
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(Post)

View File

@@ -0,0 +1,89 @@
// - Import react components
import React, {Component} from 'react'
import {connect} from 'react-redux'
import PropTypes from 'prop-types'
// - Import app components
import Blog from 'Blog'
// - Import API
// - Import actions
import * as postActions from 'postActions'
import * as userActions from 'userActions'
/**
* Create component class
*/
export class PostPage extends Component {
static propTypes = {
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props){
super(props)
//Defaul state
this.state = {
}
// Binding functions to `this`
}
componentWillMount() {
this.props.loadPost()
this.props.loadUserInfo()
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
return (
<Blog posts={this.props.posts} displayWriting={false} />
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch,ownProps) => {
const {userId,postId} = ownProps.match.params
return{
loadPost: () => dispatch(postActions.dbGetPostById(userId,postId)),
loadUserInfo: () => dispatch(userActions.dbGetUserInfoByUserId(userId,'header'))
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state,ownProps) => {
const {userId,postId} = ownProps.match.params
return{
avatar: state.user.info && state.user.info[userId] ? state.user.info[userId].avatar : '',
name: state.user.info && state.user.info[userId] ? state.user.info[userId].fullName : '',
posts: state.post.userPosts && state.post.userPosts[userId] ? {[postId] : { ...state.post.userPosts[userId][postId]}} : {}
}
}
// - Connect component to redux store
export default connect(mapStateToProps,mapDispatchToProps)(PostPage)

View File

@@ -0,0 +1,484 @@
// - Import react components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { List, ListItem } from 'material-ui/List'
import Paper from 'material-ui/Paper'
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import RaisedButton from 'material-ui/RaisedButton';
import { grey400, grey800, darkBlack, lightBlack } from 'material-ui/styles/colors'
import IconButton from 'material-ui/IconButton'
import TextField from 'material-ui/TextField'
import MenuItem from 'material-ui/MenuItem'
import SvgRemoveImage from 'material-ui/svg-icons/content/remove-circle'
import SvgCamera from 'material-ui/svg-icons/image/photo-camera'
import IconMenu from 'material-ui/IconMenu'
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'
// - Import app components
import ImageGallery from 'ImageGallery'
import Img from 'Img'
import UserAvatar from 'UserAvatar'
// - Import API
import * as AuthAPI from 'AuthAPI'
import * as PostAPI from 'PostAPI'
// - Import actions
import * as imageGalleryActions from 'imageGalleryActions'
import * as postActions from 'postActions'
// - Create PostWrite component class
export class PostWrite extends Component {
static propTypes = {
/**
* If it's true post writing page will be open
*/
open: PropTypes.bool,
/**
* Recieve request close function
*/
onRequestClose: PropTypes.func,
/**
* Post write style
*/
style: PropTypes.object,
/**
* If it's true, post will be in edit view
*/
edit: PropTypes.bool.isRequired,
/**
* The text of post in editing state
*/
text: PropTypes.string,
/**
* The image of post in editing state
*/
image: PropTypes.string,
/**
* If post state is editing this id sould be filled with post identifier
*/
id: PropTypes.string
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
// Default state
this.state = {
/**
* Post text
*/
postText: this.props.edit ? this.props.text : '',
/**
* The image of the post
*/
image: this.props.edit ? this.props.image : '',
/**
* If it's true gallery will be open
*/
galleryOpen: false,
/**
* If it's true post button will be disabled
*/
disabledPost: true,
/**
* If it's true comment will be disabled on post
*/
disableComments: this.props.edit ? this.props.disableComments : false,
/**
* If it's true share will be disabled on post
*/
disableSharing: this.props.edit ? this.props.disableSharing : false,
}
// Binding functions to `this`
this.handleOnChange = this.handleOnChange.bind(this)
this.handleCloseGallery = this.handleCloseGallery.bind(this)
this.handleOpenGallery = this.handleOpenGallery.bind(this)
this.onRequestSetImage = this.onRequestSetImage.bind(this)
this.handlePost = this.handlePost.bind(this)
this.handleRemoveImage = this.handleRemoveImage.bind(this)
this.handleToggleComments = this.handleToggleComments.bind(this)
this.handleToggleSharing = this.handleToggleSharing.bind(this)
}
/**
* Toggle comments of the post to disable/enable
*
*
* @memberof PostWrite
*/
handleToggleComments = () => {
this.setState({
disableComments: !this.state.disableComments,
disabledPost: false
})
}
/**
* Toggle sharing of the post to disable/enable
*
*
* @memberof PostWrite
*/
handleToggleSharing = () => {
this.setState({
disableSharing: !this.state.disableSharing,
disabledPost: false
})
}
/**
* Romove the image of post
*
*
* @memberof PostWrite
*/
handleRemoveImage = () => {
this.setState({
image: '',
disabledPost: false
})
}
/**
* Handle send post to the server
* @param {event} evt passed by clicking on the post button
*/
handlePost = (evt) => {
var image = this.state.image
var tags = PostAPI.getContentTags(this.state.postText)
// In edit status we should fire update if not we should fire post function
if (!this.props.edit) {
if (image !== '') {
this.props.post({
body: this.state.postText,
tags: tags,
image: image,
avatar: this.props.avatar,
name: this.props.name,
disableComments: this.state.disableComments,
disableSharing: this.state.disableSharing
}, this.props.onRequestClose)
}
else {
this.props.post({
body: this.state.postText,
tags: tags,
avatar: this.props.avatar,
name: this.props.name,
disableComments: this.state.disableComments,
disableSharing: this.state.disableSharing
}, this.props.onRequestClose)
}
}
// In edit status we pass post to update functions
else {
this.props.update({
id: this.props.id,
body: this.state.postText,
tags: tags,
image: image,
disableComments: this.state.disableComments,
disableSharing: this.state.disableSharing
}, this.props.onRequestClose)
}
}
/**
* Set post image url
*/
onRequestSetImage = (url) => {
this.setState({
image: url,
disabledPost: false
})
}
/**
* When the post text changed
* @param {event} evt is an event passed by change post text callback funciton
* @param {string} data is the post content which user writes
*/
handleOnChange = (evt, data) => {
this.setState({ postText: data })
if (data.length === 0 || data.trim() === '' || (this.props.edit && data.trim() === this.props.text)) {
this.setState({
postText: data,
disabledPost: true
})
}
else {
this.setState({
postText: data,
disabledPost: false
})
}
}
/**
* Close image gallery
*/
handleCloseGallery = () => {
this.setState({
galleryOpen: false
})
}
/**
* Open image gallery
*/
handleOpenGallery = () => {
this.setState({
galleryOpen: true
})
}
componentWillReceiveProps(nextProps) {
if(!nextProps.open){
this.setState({
/**
* Post text
*/
postText: this.props.edit ? this.props.text : '',
/**
* The image of the post
*/
image: this.props.edit ? this.props.image : '',
/**
* If it's true gallery will be open
*/
galleryOpen: false,
/**
* If it's true post button will be disabled
*/
disabledPost: true,
/**
* If it's true comment will be disabled on post
*/
disableComments: this.props.edit ? this.props.disableComments : false,
/**
* If it's true share will be disabled on post
*/
disableSharing: this.props.edit ? this.props.disableSharing : false,
})
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
const iconButtonElement = (
<IconButton
touch={true}
tooltip="more"
tooltipPosition="bottom-left"
>
<MoreVertIcon color={grey400} />
</IconButton>
)
const rightIconMenu = (
<IconMenu iconButtonElement={iconButtonElement}>
<MenuItem onClick={this.handleToggleComments} style={{ fontSize: "14px" }}>{!this.state.disableComments ? 'Disable comments' : 'Enable comments'} </MenuItem>
<MenuItem onClick={this.handleToggleSharing} style={{ fontSize: "14px" }}>{!this.state.disableSharing ? 'Disable sharing' : 'Enable sharing'}</MenuItem>
</IconMenu>
)
var postAvatar = <UserAvatar fileName={this.props.avatar} style={{ top: "8px" }} size={40} />
var author = (
<div>
<span style={{
fontSize: "14px",
paddingRight: "10px",
fontWeight: 400,
color: "rgba(0,0,0,0.87)",
textOverflow: "ellipsis",
overflow: "hidden",
paddingLeft: "50px",
lineHeight: "25px"
}}>{this.props.name}</span><span style={{
fontWeight: 100,
fontSize: "10px"
}}> | Public</span>
</div>
)
const writeActions = [
<FlatButton
label="Cancel"
primary={true}
keyboardFocused={false}
onTouchTap={this.props.onRequestClose}
style={{ color: grey800 }}
/>,
<FlatButton
label={this.props.edit ? 'UPDATE' : 'POST'}
primary={true}
keyboardFocused={false}
onTouchTap={this.handlePost}
disabled={this.state.disabledPost}
/>
]
const galleryActions = [
<FlatButton
label="Cancel"
primary={true}
keyboardFocused={false}
onTouchTap={this.handleCloseGallery}
style={{ color: grey800 }}
/>
]
const styles = {
dialog: {
width: '',
maxWidth: '530px',
borderRadius: "4px"
}
}
return (
<div style={this.props.style}>
{this.props.children}
<Dialog
id= {this.props.id || 0}
actions={writeActions}
modal={false}
open={this.props.open}
contentStyle={styles.dialog}
onRequestClose={this.props.onRequestClose}
overlayStyle={{ background: "rgba(0,0,0,0.12)" }}
bodyStyle={{ padding: 0 }}
autoDetectWindowHeight={false}
actionsContainerStyle={{ borderTop: "1px solid rgb(224, 224, 224)" }}
>
<ListItem
disabled={true}
leftAvatar={postAvatar}
rightIconButton={rightIconMenu}
primaryText={author}
style={{ padding: "16px 4px 30px 16px" }}
/>
<div style={{ display: "flex", flexDirection: "column", flexGrow: 1, overflow: "hidden" }}>
<div style={{ position: "relative", flexDirection: "column", display: "flex", flexGrow: 1, overflow: "hidden", overflowY: "auto", maxHeight: "300px" }}>
<TextField
value={this.state.postText}
onChange={this.handleOnChange}
hintText="What is new with you?"
underlineShow={false}
multiLine={true}
rows={2}
hintStyle={{ fontWeight: 200, fontSize: "14px" }}
textareaStyle={{ fontWeight: 200, fontSize: "14px" }}
style={{ margin: "0 16px", flexShrink: 0, width: "initial", flexGrow: 1 }}
/>
{(this.state.image && this.state.image !== '')
? (<div>
<div style={{ position: "relative", overflowY: "hidden", overflowX: "auto" }}>
<ul style={{ position: "relative", whiteSpace: "nowrap", padding: "0 0 0 16px", margin: "8px 0 0 0", paddingRight: "16px", verticalAlign: "bottom", flexShrink: 0, listStyleType: "none" }}>
<div style={{ display: "flex", position: "relative" }}>
<span onClick={this.handleRemoveImage} style={{
position: "absolute", width: "28px", backgroundColor: "rgba(255, 255, 255, 0.22)",
height: "28px", right: 12, top: 4, cursor: "pointer", borderRadius: "50%",
display: "flex", alignItems: "center", justifyContent: "center"
}}>
<SvgRemoveImage hoverColor="rgba(0, 0, 0, 0.65)" style={{ color: "rgba(0, 0, 0, 0.53)" }} />
</span>
<div style={{ display: "inline-block", width: "100%", marginRight: "8px", transition: "transform .25s" }}>
<li style={{ width: "100%", margin: 0, verticalAlign: "bottom", position: "static" }}>
<Img fileName={this.state.image} style={{ width: "100%", height: "auto" }} />
</li>
</div>
</div>
</ul>
</div>
</div>) : ''}
</div>
<div style={{ flexShrink: 0, boxFlex: 0, flexGrow: 0, maxHeight: "48px", width: "100%" }}>
<div style={{ flexDirection: "row", display: "flex" }}>
<div onClick={this.handleOpenGallery} style={{ outline: "none", width: "48px", zIndex: 0, overflow: "hidden", position: "relative", textAlign: "center", transition: "background .3s", border: 0, borderRadius: "50%", display: "inlineBlock", height: "48px" }}>
<span style={{ top: "15px", display: "block", position: "relative", cursor: "pointer" }}>
<SvgCamera color="grey" />
</span>
</div>
</div>
</div>
</div>
</Dialog>
<Dialog
actions={galleryActions}
modal={false}
open={this.state.galleryOpen}
contentStyle={styles.dialog}
onRequestClose={this.handleCloseGallery}
overlayStyle={{ background: "rgba(0,0,0,0.12)" }}
autoDetectWindowHeight={false}
>
<ImageGallery set={this.onRequestSetImage} close={this.handleCloseGallery} />
</Dialog>
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
post: (post, callBack) => dispatch(postActions.dbAddImagePost(post, callBack)),
update: (post,callBack) => dispatch(postActions.dbUpdatePost(post, callBack))
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
postImageState: state.imageGallery.status,
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : '',
name: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].fullName : ''
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(PostWrite)

165
app/components/Profile.jsx Normal file
View File

@@ -0,0 +1,165 @@
// - Import react components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import Dialog from 'material-ui/Dialog'
import FlatButton from 'material-ui/FlatButton'
import RaisedButton from 'material-ui/RaisedButton'
// - Import app components
import ProfileHead from 'ProfileHead'
import Blog from 'Blog'
// - Import API
// - Import actions
import * as postActions from 'postActions'
import * as userActions from 'userActions'
import * as globalActions from 'globalActions'
/**
* Create component class
*/
export class Profile extends Component {
static propTypes = {
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
//Defaul state
this.state = {
}
// Binding functions to `this`
}
componentWillMount() {
this.props.loadPosts()
this.props.loadUserInfo()
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
/**
* Component styles
*/
const styles = {
profile: {
margin: '0 auto',
width: '90%'
},
header: {
},
content: {
},
showcover: {
height: '450px'
},
avatar: {
border: '2px solid rgb(255, 255, 255)'
}
}
/**
* Actions for material ui dialog
*/
const actions = [
<FlatButton
label="Cancel"
primary={true}
onTouchTap={this.handleClose}
/>,
<FlatButton
label="Submit"
primary={true}
keyboardFocused={true}
onTouchTap={this.handleClose}
/>
]
return (
<div style={styles.profile}>
<div style={styles.header}>
<ProfileHead avatar={this.props.avatar} isAuthedUser={this.props.isAuthedUser} banner={this.props.banner} fullName={this.props.name} followerCount={0} userId={this.props.userId}/>
</div>
{this.props.posts && Object.keys(this.props.posts).length !==0
? (<div style={styles.content}>
<div className='profile__title'>
{this.props.name}'s posts
</div>
<div style={{ height: '24px' }}></div>
<Blog posts={this.props.posts} displayWriting={false} />
</div>)
: (<div className='profile__title'>
Nothing shared
</div>)
}
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
const { userId } = ownProps.match.params
return {
loadPosts: () => dispatch(postActions.dbGetPostsByUserId(userId)),
loadUserInfo: () => dispatch(userActions.dbGetUserInfoByUserId(userId, 'header'))
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
const { userId } = ownProps.match.params
const {uid} = state.authorize
return {
avatar: state.user.info && state.user.info[userId] ? state.user.info[userId].avatar || '' : '',
name: state.user.info && state.user.info[userId] ? state.user.info[userId].fullName || '' : '',
banner: state.user.info && state.user.info[userId] ? state.user.info[userId].banner || '' : '',
posts: state.post.userPosts ? state.post.userPosts[userId] : {},
isAuthedUser: userId === uid,
userId
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(Profile)

View File

@@ -0,0 +1,246 @@
// - Import react components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { grey400, darkBlack, lightBlack } from 'material-ui/styles/colors'
import IconButton from 'material-ui/IconButton'
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'
import IconMenu from 'material-ui/IconMenu'
import MenuItem from 'material-ui/MenuItem'
import FlatButton from 'material-ui/FlatButton'
import RaisedButton from 'material-ui/RaisedButton'
import EventListener, { withOptions } from 'react-event-listener'
import { Parallax, Background } from 'react-parallax'
// - Import app components
import ImgCover from 'ImgCover'
import EditProfile from 'EditProfile'
import UserAvatar from 'UserAvatar'
// - Import API
// - Import actions
import * as globalActions from 'globalActions'
import * as userActions from 'userActions'
/**
* Create component class
*/
export class ProfileHead extends Component {
static propTypes = {
/**
* User avatar address
*/
avatar: PropTypes.string,
/**
* User avatar address
*/
banner: PropTypes.string,
/**
* User full name
*/
fullName: PropTypes.string.isRequired,
/**
* The number of followers
*/
followerCount: PropTypes.number,
/**
* User identifier
*/
userId: PropTypes.string,
/**
* If the user profile identifier of param is equal to the user authed identifier
*/
isAuthedUser: PropTypes.bool
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
/**
* Defaul state
*/
this.state = {
/**
* If it's true , the window is in small size
*/
isSmall: false,
}
// Binding functions to `this`
}
/**
* Handle resize event for window to change sidebar status
* @param {event} evt is the event is passed by winodw resize event
*/
handleResize = (evt) => {
// Set initial state
var width = window.innerWidth
if (width > 900) {
this.setState({
isSmall: false
})
}
else {
this.setState({
isSmall: true
})
}
}
componentDidMount = () => {
this.handleResize()
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
const styles = {
avatar: {
border: '2px solid rgb(255, 255, 255)'
},
iconButton: {
fill: 'rgb(255, 255, 255)',
height: '24px',
width: '24px',
},
iconButtonSmall: {
fill: 'rgb(0, 0, 0)',
height: '24px',
width: '24px',
},
editButton: {
marginLeft: '20px'
},
editButtonSmall: {
marginLeft: '20px',
color: 'white',
fill: 'blue'
},
aboutButton: {
color: 'white'
},
aboutButtonSmall: {
color: 'black'
}
}
const iconButtonElement = (
<IconButton style={this.state.isSmall ? styles.iconButtonSmall : styles.iconButton} iconStyle={this.state.isSmall ? styles.iconButtonSmall : styles.iconButton}
touch={true}
>
<MoreVertIcon color={grey400} viewBox='10 0 24 24' />
</IconButton>
)
const RightIconMenu = () => (
<IconMenu iconButtonElement={iconButtonElement}>
<MenuItem style={{ fontSize: "14px" }}>Reply</MenuItem>
<MenuItem style={{ fontSize: "14px" }}>Edit</MenuItem>
<MenuItem style={{ fontSize: "14px" }}>Delete</MenuItem>
</IconMenu>
)
const {isAuthedUser} = this.props
return (
<div>
<Parallax strength={500} className="profile__parallax" bgStyle={{ position: 'relative' }}>
<Background>
<ImgCover width='100%' height='510px' borderRadius='2px' fileName={this.props.banner || '13e1fc2f-0484-42ef-a61f-75f4b5b20a37.jpeg'} />
</Background>
</Parallax>
<div className={this.state.isSmall ? 'profile__head-info-s' : 'profile__head-info'}>
<EventListener
target="window"
onResize={this.handleResize}
/>
<div className='left'>
{/* User avatar*/}
<div style={{ display: 'flex', justifyContent: 'center' }}><UserAvatar fileName={this.props.avatar} size={60} style={styles.avatar} /></div>
<div className='info'>
<div className='fullName'>
{this.props.fullName}
</div>
{/*<div className='followers'>
{this.props.followerCount} Followers
</div>*/}
</div>
</div>
<div className='right'>
{isAuthedUser ? (<div style={this.state.isSmall ? styles.editButtonSmall : styles.editButton}><RaisedButton label="EDIT PROFILE" onClick={this.props.openEditor} /></div>) : ''}
</div>
</div>
{isAuthedUser ? (<EditProfile
onRequestClose={this.handleCloseEditor}
avatar={this.props.avatar}
banner={this.props.banner}
fullName={this.props.fullName}
/>): ''}
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
openEditor: () => dispatch(userActions.openEditProfile())
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(ProfileHead)

223
app/components/Settings.jsx Normal file
View File

@@ -0,0 +1,223 @@
// - Import external components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { NavLink, withRouter } from 'react-router-dom'
import { push } from 'react-router-redux'
import Paper from 'material-ui/Paper'
import TextField from 'material-ui/TextField'
import RaisedButton from 'material-ui/RaisedButton'
import FlatButton from 'material-ui/FlatButton'
// - Import actions
import * as authorizeActions from 'authorizeActions'
/**
* Create component class
*
* @export
* @class Settings
* @extends {Component}
*/
export class Settings extends Component {
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props);
this.state = {
passwordInput: '',
passwordInputError: '',
confirmInput: '',
confirmInputError: '',
}
// Binding function to `this`
this.handleForm = this.handleForm.bind(this)
}
/**
* Handle data on input change
* @param {event} evt is an event of inputs of element on change
*/
handleInputChange = (evt) => {
const target = evt.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
switch (name) {
case 'passwordInput':
this.setState({
passwordInputError: ''
})
break
case 'confirmInput':
this.setState({
confirmInputError: '',
passwordInputError: ''
})
break;
default:
}
}
/**
* Handle register form
*/
handleForm = () => {
var error = false
if (this.state.passwordInput === '') {
this.setState({
passwordInputError: 'This field is required'
})
error = true
}
else if (this.state.confirmInput === '') {
this.setState({
confirmInputError: 'This field is required'
})
error = true
}
else if(this.state.confirmInput !== this.state.passwordInput) {
this.setState({
confirmInputError: 'Password and confirm password should be equal!'
})
error = true
}
if (!error) {
this.props.login(
this.state.passwordInput,
this.state.confirmInput
)
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
const paperStyle = {
minHeight: 370,
width: 450,
textAlign: 'center',
display: 'block',
margin: "auto"
};
return (
<div>
<h1 style={{
textAlign: "center",
padding: "20px",
fontSize: "30px",
fontWeight: 500,
lineHeight: "32px",
margin: "auto",
color: "rgba(138, 148, 138, 0.2)"
}}>Green</h1>
<div className="animate-bottom">
<Paper style={paperStyle} zDepth={1} rounded={false} >
<div style={{ padding: "48px 40px 36px" }}>
<div style={{
paddingLeft: "40px",
paddingRight: "40px"
}}>
<h2 style={{
textAlign: "left",
paddingTop: "16px",
fontSize: "24px",
fontWeight: 400,
lineHeight: "32px",
margin: 0
}}>Change Password</h2>
</div>
<TextField
onChange={this.handleInputChange}
errorText={this.state.passwordInputError}
name="passwordInput"
floatingLabelStyle={{ fontSize: "15px" }}
floatingLabelText="New password"
type="password"
/><br />
<TextField
onChange={this.handleInputChange}
errorText={this.state.confirmInputError}
name="confirmInput"
floatingLabelStyle={{ fontSize: "15px" }}
floatingLabelText="Confirm password"
type="password"
/><br />
<br />
<br />
<div className="settings__button-box">
<div>
<FlatButton label="Home" onClick={this.props.homePage} />
</div>
<div>
<RaisedButton label="Change password" primary={true} onClick={this.handleForm} />
</div>
</div>
</div>
</Paper>
</div>
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
login: (password) => {
dispatch(authorizeActions.dbUpdatePassword(password))
},
homePage: () => {
dispatch(push("/"))
}
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
}
}
// - Connect component to redux store
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Settings))

296
app/components/Sidebar.jsx Normal file
View File

@@ -0,0 +1,296 @@
// - Import react components
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'
import { connect } from 'react-redux'
import EventListener, { withOptions } from 'react-event-listener'
import keycode from 'keycode'
// - Import app components
// - Import API
import * as AuthAPI from 'AuthAPI'
// - Import actions
import * as authorizeActions from 'authorizeActions'
import * as globalActions from 'globalActions'
// - Feilds
const color = 'teal';
const colorKey = 'blue';
const sizeCondition = (width) => (width >= 750)
// - Create Sidebar component class
export class Sidebar extends Component {
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
// Default state
this.state = {
sidebarClass: "",
overlay: false,
mainStyle: { marginLeft: "210px" },
// Is sidebar open or not
open: true,
// If sidebar is closed by resizing or not
auto: false,
// If overlay should be open or not
overlayOpen: false,
// If side bar should be closed
shouldBeClosed: false,
}
// Binding functions to `this`
this.handleLogout = this.handleLogout.bind(this)
this.open = this.open.bind(this)
this.getChildren = this.getChildren.bind(this)
}
/**
* Handle open sidebar
* @param {boolean} status if is true, sidebar will be open
* @param {string} source is the element that fired the function
*/
open = (status, source) => {
const width = window.innerWidth
if (status) {
// Sidebar style when it's open
const openStyle = {
width: "210px",
transform: "translate(0px, 0px)",
transition: "transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms"
}
this.setState({
open: true,
mainStyle: { marginLeft: "210px" },
sidebarStyle: openStyle,
sidebarClass: (sizeCondition(width)) ? "sidebar sidebar__large" : "sidebar sidebar__over",
overlay: (sizeCondition(width)) ? false : true
})
if (sizeCondition(width)) {
this.setState({
auto: false,
shouldBeClosed: false
})
} else {
this.setState({
overlayOpen: true
})
}
/**
* Callback function fired to determine sidebar and overlay sidebar status
* @param {boolean} if true, the sidebar is open
*/
this.props.status(true)
}
// If it's false sidebar should be closed
else {
// Sidebar style when it's closed
const closeStyle = {
transform: "translate(-100%, 0px)",
transition: "transform 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms"
}
this.setState({
open: false,
mainStyle: { marginLeft: "0px" },
sidebarStyle: closeStyle,
sidebarClass: (sizeCondition(width)) ? "sidebar sidebar__large"
: ((source === 'auto') ? "sidebar " : "sidebar sidebar__over"),
overlay: false
})
switch (source) {
case 'auto':
this.setState({
auto: true
})
break;
case 'overlay':
this.setState({
shouldBeClosed: true
})
break;
default:
}
if (sizeCondition(width)) {
// TODO: Get ride of this
} else {
this.setState({
overlayOpen: false
})
}
/**
* Callback function fired to determine sidebar and overlay sidebar status
* @param {boolean} if true, the sidebar is open
*/
this.props.status(false)
}
this.props.overlay((sizeCondition(width)) ? false : true)
}
/**
* Handle resize event for window to change sidebar status
* @param {event} evt is the event is passed by winodw resize event
*/
handleResize = (evt) => {
// Set initial state
var width = window.innerWidth
if (sizeCondition(width)) {
this.setState({
sidebarClass: "sidebar sidebar__large",
overlay: false,
overlayOpen: false
})
this.props.overlay(false)
if (this.state.auto && !this.state.shouldBeClosed) {
this.open(true)
this.setState({ auto: false })
}
}
else {
if (!this.state.overlayOpen) {
if (!this.state.auto && this.state.open) {
this.open(false, 'auto')
}else{
this.setState({
overlayOpen:true,
overlay:true
})
}
} else {
this.setState({ sidebarClass: "sidebar sidebar__over", overlay: true })
this.props.overlay(true)
}
}
}
/**
* Handle logout user
*/
handleLogout = () => {
var { dispatch } = this.props
dispatch(authorizeActions.dbLogout())
}
/**
* Handle keyup event for window to close sidebar
* @param {event} evt is the event is passed by winodw key event
*/
handleKeyUp = (evt) => {
if (this.state.overlayOpen) {
if (this.state.open && keycode(event) === 'esc') {
this.open(false);
}
}
}
componentWillMount = () => {
this.props.open(this.open)
}
getChildren = () => {
return React.Children.map(this.props.children, (childe) => {
if (childe.type.qcName === 'SidebarContent') {
const sideBarContent = React.cloneElement(childe, {
className: this.state.sidebarClass,
cstyle: this.state.sidebarStyle,
sidebar: this.open,
overlay: this.state.overlay
})
return sideBarContent
}
else if (childe.type.qcName === 'SidebarMain') {
return React.cloneElement(childe, { cstyle: this.state.mainStyle })
}
})
}
componentDidMount = () => {
this.handleResize()
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
return (
<div id='sidebar'>
<EventListener
target="window"
onResize={this.handleResize}
onKeyUp={this.handleKeyUp}
/>
{this.getChildren()}
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(Sidebar)

View File

@@ -0,0 +1,54 @@
// - Import react components
import React, {Component} from 'react'
// - Import components
// - Import actions
// - Create component class
export default class SidebarContent extends Component {
static qcName= 'SidebarContent'
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
// Default state
this.state = {}
// Binding function to `this`
this.handleClickCover = this.handleClickCover.bind(this)
}
/**
* Handle click on cover of sidebar
* @param {event} evt is a click event passed to funciton
*/
handleClickCover = (evt) => {
this.props.sidebar(false,'overlay')
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
let showCoverStyle = {position: "fixed", height: "100%", width: "100%", top: "0px", left: "0px", opacity: "1", backgroundColor: "rgba(255, 255, 255, 0.54)", WebkitTapHighlightColor: "rgba(0, 0, 0, 0)", willChange: "opacity", transform: "translateZ(0px)", transition: "left 0ms cubic-bezier(0.23, 1, 0.32, 1) 0ms, opacity 400ms cubic-bezier(0.23, 1, 0.32, 1) 0ms", zIndex: "1111", pointerEvents: "auto"}
let hideCoverStyle = {position: "fixed", height: "100%", width: "100%", top: "0px", left: "-100%", opacity: "0", backgroundColor: "rgba(255, 255, 255, 0.54)", WebkitTapHighlightColor: "rgba(0, 0, 0, 0)", willChange: "opacity", transform: "translateZ(0px)", transition: "left 0ms cubic-bezier(0.23, 1, 0.32, 1) 400ms, opacity 400ms cubic-bezier(0.23, 1, 0.32, 1) 0ms", zIndex: "1111", pointerEvents: "none"}
return (
<div id='sidebar-content'>
<div style={this.props.overlay ? showCoverStyle : hideCoverStyle} style={{overflow:'hidden'}} onClick={this.handleClickCover}></div>
<div className={this.props.className} style={this.props.cstyle}>
{this.props.children}
</div>
</div>
)
}
}

View File

@@ -0,0 +1,36 @@
// - Import react components
import React, {Component} from 'react'
// - Import components
// - Import actions
// - Create component class
export default class SidebarMain extends Component {
static qcName = 'SidebarMain'
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
// Default state
this.state = {}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
return (
<div className="home__main" style={this.props.cstyle} >
<div style={{height:"64px", width:"100%"}}></div>
{this.props.children}
</div>
)
}
}

271
app/components/Signup.jsx Normal file
View File

@@ -0,0 +1,271 @@
// - Import react components
import React,{Component} from 'react'
import {connect} from 'react-redux'
import {push} from 'react-router-redux'
import {NavLink, withRouter} from 'react-router-dom'
import Paper from 'material-ui/Paper'
import TextField from 'material-ui/TextField'
import RaisedButton from 'material-ui/RaisedButton'
import FlatButton from 'material-ui/FlatButton'
// - Import actions
import * as authorizeActions from 'authorizeActions'
import * as globalActions from 'globalActions'
// - Create Signup componet class
export class Signup extends Component {
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props){
super(props);
this.state = {
fullNameInput: '',
fullNameInputError: '',
emailInput: '',
emailInputError: '',
passwordInput: '',
passwordInputError: '',
confirmInput: '',
confirmInputError: ''
}
// Binding function to `this`
this.handleForm = this.handleForm.bind(this)
}
/**
* Handle data on input change
* @param {event} evt is an event of inputs of element on change
*/
handleInputChange = (evt) => {
const target = evt.target
const value = target.type === 'checkbox' ? target.checked : target.value
const name = target.name
this.setState({
[name]: value
})
switch (name) {
case 'fullNameInput':
this.setState({
fullNameInputError: '',
})
break
case 'emailInput':
this.setState({
emailInputError: ''
})
break
case 'passwordInput':
this.setState({
confirmInputError: '',
passwordInputError: ''
})
break
case 'confirmInput':
this.setState({
confirmInputError: '',
passwordInputError: ''
})
break
case 'checkInput':
this.setState({
checkInputError: ''
})
break;
default:
}
}
/**
* Handle register form
*/
handleForm = () => {
var error = false
if (this.state.fullNameInput === '') {
this.setState({
fullNameInputError: 'This field is required'
})
error = true
}
if (this.state.emailInput === '') {
this.setState({
emailInputError: 'This field is required'
})
error = true
}
if (this.state.passwordInput === '') {
this.setState({
passwordInputError: 'This field is required'
})
error = true
}
if (this.state.confirmInput === '') {
this.setState({
confirmInputError: 'This field is required'
})
error = true
}
else if(this.state.confirmInput !== this.state.passwordInput){
this.setState({
passwordInputError: 'This field sould be equal to confirm password',
confirmInputError: 'This field sould be equal to password'
})
error = true
}
if (!error) {
this.props.register({
email: this.state.emailInput,
password: this.state.passwordInput,
fullName: this.state.fullNameInput
})
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
const paperStyle = {
minHeight: 500,
width: 450,
margin: 20,
textAlign: 'center',
display: 'block',
margin: "auto"
};
return (
<div>
<h1 style={{
textAlign: "center",
padding:"20px",
fontSize: "30px",
fontWeight: 500,
lineHeight: "32px",
margin: "auto",
color: "rgba(138, 148, 138, 0.2)"
}}>Green</h1>
<div className="animate-bottom">
<Paper style={paperStyle} zDepth={1} rounded={false} >
<div style={{padding: "48px 40px 36px"}}>
<div style={{paddingLeft: "40px",
paddingRight: "40px"}}>
<h2 style={{
textAlign: "left",
paddingTop: "16px",
fontSize: "24px",
fontWeight: 400,
lineHeight: "32px",
margin: 0}}>Sign up</h2>
</div>
<TextField
onChange={this.handleInputChange}
errorText={this.state.fullNameInputError}
name="fullNameInput"
floatingLabelStyle={{fontSize:"15px"}}
floatingLabelText="Full Name"
type="text"
/><br />
<TextField
onChange={this.handleInputChange}
errorText={this.state.emailInputError}
name="emailInput"
floatingLabelStyle={{fontSize:"15px"}}
floatingLabelText="Email"
type="email"
/><br />
<TextField
onChange={this.handleInputChange}
errorText={this.state.passwordInputError }
name="passwordInput"
floatingLabelStyle={{fontSize:"15px"}}
floatingLabelText="Password"
type="password"
/><br />
<TextField
onChange={this.handleInputChange}
errorText={this.state.confirmInputError }
name="confirmInput"
floatingLabelStyle={{fontSize:"15px"}}
floatingLabelText="Confirm Password"
type="password"
/><br />
<br />
<div className="signup__button-box">
<div>
<FlatButton label="Login" onClick={this.props.loginPage} />
</div>
<div>
<RaisedButton label="Create" primary={true} onClick={this.handleForm}/>
</div>
</div>
</div>
</Paper>
</div>
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch,ownProps) => {
return {
showError: (message) => {
dispatch(action(types.SHOW_ERROR_MESSAGE_GLOBAL)(error.message))
},
register: (data) => {
dispatch(authorizeActions.dbSignup(data))
},
loginPage: () => {
dispatch(push("/login"))
}
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state,ownProps) => {
return{
}
}
// - Connect component to redux store
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(Signup))

View File

@@ -0,0 +1,129 @@
// - Import react components
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import Avatar from 'material-ui/Avatar'
// - Import app components
// - Import API
// - Import actions
import * as imageGalleryActions from 'imageGalleryActions'
/**
* Create component class
*/
export class ImgCover extends Component {
static propTypes = {
/**
* Use for getting url address from server
*/
fileName: PropTypes.string.isRequired,
/**
* Avatar style
*/
style: PropTypes.object,
/**
* Avatar size
*/
size: PropTypes.number,
/**
* Trigger on touch tap
*/
onTouchTap: PropTypes.func
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
//Defaul state
this.state = {
}
// Binding functions to `this`
this.getImageURL = this.getImageURL.bind(this)
}
componentWillReceiveProps(nextProps) {
const { fileName, avatarURL } = this.props
if (fileName && !avatarURL[fileName]) {
if (this.props.imageRequests.indexOf(fileName) > -1)
return
this.getImageURL()
}
}
getImageURL = () => {
let { fileName } = this.props
if (fileName && fileName !== '') {
if (this.props.imageRequests.indexOf(fileName) > -1)
return
this.props.getImage(fileName)
}
}
componentWillMount() {
let { fileName } = this.props
if (this.props.imageRequests.indexOf(fileName) > -1)
return
this.getImageURL()
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
let { fileName, style, size, onTouchTap, avatarURL } = this.props
return (
<Avatar src={avatarURL[fileName] || ''} size={size || 36} style={style} onTouchTap={onTouchTap} />
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
getImage: (name) => dispatch(imageGalleryActions.dbDownloadImage(name))
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
avatarURL: state.imageGallery.imageURLList,
imageRequests: state.imageGallery.imageRequests
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(ImgCover)

293
app/components/UserBox.jsx Normal file
View File

@@ -0,0 +1,293 @@
// - Import react components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import {push} from 'react-router-redux'
import Paper from 'material-ui/Paper'
import FlatButton from 'material-ui/FlatButton'
import RaisedButton from 'material-ui/RaisedButton';
import Popover, { PopoverAnimationVertical } from 'material-ui/Popover';
import Menu from 'material-ui/Menu';
import MenuItem from 'material-ui/MenuItem'
import Checkbox from 'material-ui/Checkbox'
import TextField from 'material-ui/TextField'
// - Import app components
import UserAvatar from 'UserAvatar'
// - Import API
import CircleAPI from 'CircleAPI'
// - Import actions
import * as circleActions from 'circleActions'
/**
* Create component class
*/
export class UserBox extends Component {
static propTypes = {
/**
* User identifier
*/
userId: PropTypes.string,
/**
* User information
*/
user:PropTypes.object
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
//Defaul state
this.state = {
/**
* It will be true if user follow popover is open
*/
open: false,
/**
* The value of circle input
*/
circleName: '',
/**
* It will be true if the text field for adding group is empty
*/
disabledAddCircle: true
}
// Binding functions to `this`
this.handleChangeName = this.handleChangeName.bind(this)
this.handleCreateCricle = this.handleCreateCricle.bind(this)
this.handleFollowUser = this.handleFollowUser.bind(this)
}
handleFollowUser = (checked,cid) => {
const {userId,user} = this.props
const {avatar,fullName} = user
if (checked) {
this.props.addFollowingUser(cid,{avatar,userId,fullName})
} else {
this.props.deleteFollowingUser(cid,userId)
}
}
/**
* Handle create circle
*
*
* @memberof UserBox
*/
handleCreateCricle = () => {
const {circleName} = this.state
if(circleName && circleName.trim() !== '' ){
this.props.createCircle(this.state.circleName)
this.setState({
circleName: '',
disabledAddCircle: true
})}
}
/**
* Handle change group name input to the state
*
*
* @memberof UserBox
*/
handleChangeName = (evt) => {
this.setState({
circleName: evt.target.value,
disabledAddCircle: (evt.target.value === undefined || evt.target.value.trim() === '')
})
}
/**
* Handle touch tab on follow popover
*
*
* @memberof UserBox
*/
handleTouchTap = (evt) => {
// This prevents ghost click.
event.preventDefault();
this.setState({
open: true,
anchorEl: evt.currentTarget,
})
}
/**
* Handle close follow popover
*
*
* @memberof UserBox
*/
handleRequestClose = () => {
this.setState({
open: false,
})
}
circleList = () => {
let { circles, userId, userBelongCircles } = this.props
if (circles) {
return Object.keys(circles).map((key, index) => {
if(key.trim() !== '-Followers'){
let isBelong = userBelongCircles.indexOf(key) > -1
return <Checkbox
key={key}
style={{ padding: '10px' }}
label={circles[key].name}
labelStyle={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
width: '100%'
}}
onCheck={(evt,checked) => this.handleFollowUser(checked,key)}
checked={isBelong}
/>}
})
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
const styles = {
paper: {
height: 254,
width: 243,
margin: 10,
textAlign: 'center',
maxWidth: '257px'
},
followButton: {
position: 'absolute',
bottom: '8px',
left: 0,
right: 0
}
}
return (
<Paper style={styles.paper} zDepth={1} className='grid-cell'>
<div style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'flex-start',
height: '100%',
position: 'relative',
padding: '30px'
}}>
<div onClick={() => this.props.goTo(`/${this.props.userId}`)} style={{cursor:'pointer'}}>
<UserAvatar
fileName={this.props.avatar}
size={90}
/>
</div>
<div onClick={() => this.props.goTo(`/${this.props.userId}`)} className='people__name' style={{cursor:'pointer'}}>
<div>
{this.props.user.fullName}
</div>
</div>
<div style={styles.followButton}>
<FlatButton
label={(this.props.belongCirclesCount && this.props.belongCirclesCount < 1) ? 'Follow'
: (this.props.belongCirclesCount > 1 ? `${this.props.belongCirclesCount} Circles` : ((this.props.firstBelongCircle) ? this.props.firstBelongCircle.name : 'Follow'))}
primary={true}
onTouchTap={this.handleTouchTap}
/>
</div>
</div>
<Popover
open={this.state.open}
anchorEl={this.state.anchorEl}
anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }}
targetOrigin={{ horizontal: 'left', vertical: 'top' }}
onRequestClose={this.handleRequestClose}
animation={PopoverAnimationVertical}
>
<Menu >
<div style={{
position: 'relative',
display: 'block',
maxHeight: '220px'
}}>
<div style={{ overflowY: 'auto', height: '100%' }}>
{this.circleList()}
</div>
</div>
<div style={{ padding: '10px' }}>
<TextField
hintText="Group name"
onChange={this.handleChangeName}
value={this.state.circleName}
/><br />
<FlatButton label="ADD" primary={true} disabled={this.state.disabledAddCircle} onClick={this.handleCreateCricle} />
</div>
</Menu>
</Popover>
</Paper>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
createCircle: (name) => dispatch(circleActions.dbAddCircle(name)),
addFollowingUser: (cid,user) => dispatch(circleActions.dbAddFollowingUser(cid,user)),
deleteFollowingUser: (cid,followingId) => dispatch(circleActions.dbDeleteFollowingUser(cid,followingId)),
goTo: (url)=> dispatch(push(url))
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
const { uid } = state.authorize
const circles = state.circle ? (state.circle.userCircles[uid]|| {}) : {}
const userBelongCircles = CircleAPI.getUserBelongCircles(circles,ownProps.userId)
return {
circles: circles,
userBelongCircles: userBelongCircles || [],
belongCirclesCount: userBelongCircles.length || 0,
firstBelongCircle: userBelongCircles ? (circles ? circles[userBelongCircles[0]] : {}) : {},
avatar: state.user.info && state.user.info[ownProps.userId] ? state.user.info[ownProps.userId].avatar || '' : ''
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(UserBox)

View File

@@ -0,0 +1,102 @@
// - Import react components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
// - Import app components
import UserBox from 'UserBox'
// - Import API
// - Import actions
/**
* Create component class
*/
export class UserBoxList extends Component {
static propTypes = {
/**
* List of users
*/
users: PropTypes.object
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
//Defaul state
this.state = {
}
// Binding functions to `this`
}
userList = () => {
let { users, uid } = this.props
if (users) {
return Object.keys(users).map((key, index) => {
if(uid !== key)
return <UserBox key={key} userId={key} user={users[key]}/>
})
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
const styles = {
}
return (
<div className='grid grid__1of4 grid__space-around'>
{this.userList()}
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
const {uid} = state.authorize
return {
uid
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(UserBoxList)

View File

@@ -0,0 +1,113 @@
// - Import react components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import { List } from 'material-ui/List'
// - Import app components
import Circle from 'Circle'
// - Import API
// - Import actions
/**
* Create component class
*/
export class YourCircles extends Component {
static propTypes = {
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
//Defaul state
this.state = {
}
// Binding functions to `this`
}
circleList = () => {
let { circles,uid } = this.props
let parsedCircles = []
if (circles) {
Object.keys(circles).map((key, index) => {
if(key.trim() !== '-Followers')
parsedCircles.push(<Circle key={key} circle={circles[key]} id={key} uid={uid} />)
})
}
return parsedCircles
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
let circleItems = this.circleList()
return (
<div style={{
maxWidth: '800px',
margin: '40px auto'
}}>
{(circleItems && circleItems.length !== 0 ) ? (<div>
<div className='profile__title'>
Your circles
</div>
<List>
{circleItems}
</List>
<div style={{ height: '24px' }}></div>
</div>) : ''}
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
const { uid } = state.authorize
return {
uid,
circles: state.circle ? state.circle.userCircles[uid] : {},
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(YourCircles)

63
app/components/pattern Normal file
View File

@@ -0,0 +1,63 @@
// - Import react components
import React, {Component} from 'react'
// - Import components
// - Import actions
// - Create component class
export default class {component name} extends Component {
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props){
super(props)
// Default state
this.state = {
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render(){
return(
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch,ownProps) => {
return{
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state,ownProps) => {
return{
}
}
// - Connect component to redux store
export default connect(mapStateToProps,mapDispatchToProps)(Sidebar)