Migrate components to typescript

This commit is contained in:
Qolzam
2017-10-30 20:48:18 +07:00
parent 97c2e0f157
commit 7bbb1679ad
346 changed files with 6045 additions and 3946 deletions

4
.gitignore vendored
View File

@@ -1,4 +1,6 @@
node_modules/ node_modules/
public/bundle.js public/bundle.js
config/ config/
.vscode/ .vscode/
src/data/awsClient
src/components/AWS.tsx

View File

@@ -1,51 +0,0 @@
/**
* Get the circles which the specify users is in that circle
* @param {object} circles
* @param {string} followingId
*/
export const getUserBelongCircles = (circles,followingId) => {
let userBelongCircles = []
Object.keys(circles).forEach((cid) => {
if(cid.trim() !== '-Followers' && circles[cid].users){
let isExist = Object.keys(circles[cid].users).indexOf(followingId) > -1
if(isExist){
userBelongCircles.push(cid)
}
}
})
return userBelongCircles
}
/**
* Get the following users
* @param {object} circles
*/
export const getFollowingUsers = (circles) => {
let followingUsers = {}
Object.keys(circles).forEach((cid) => {
if(cid.trim() !== '-Followers' && circles[cid].users){
Object.keys(circles[cid].users).forEach((userId)=>{
let isExist = Object.keys(followingUsers).indexOf(userId) > -1
if(!isExist){
followingUsers[userId] = {
...circles[cid].users[userId]
}
}
})
}
})
return followingUsers
}
export default {
getUserBelongCircles,
getFollowingUsers
}

View File

@@ -1,144 +0,0 @@
// - Import react component
import { storageRef } from 'app/firebaseClient/'
//- Import actions
// - Get file Extension
const getExtension = (fileName) => {
let re = /(?:\.([^.]+))?$/
return re.exec(fileName)[1]
}
// Converts image to canvas returns new canvas element
const convertImageToCanvas = (image) => {
let canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
canvas.getContext('2d').drawImage(image, 0, 0)
return canvas
}
/**
* Upload image on the server
* @param {file} file
* @param {string} fileName
*/
const uploadImage = (file, fileName, progress) => {
return new Promise((resolve, reject) => {
// Create a storage refrence
let storegeFile = storageRef.child(`images/${fileName}`)
// Upload file
let task = storegeFile.put(file)
task.then((result) => {
resolve(result)
}).catch((error) => {
reject(error)
})
// Upload storage bar
task.on('state_changed', (snapshot) => {
let percentage = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
progress(percentage, true)
}, (error) => {
console.log('========== Upload Image ============')
console.log(error)
console.log('====================================')
}, (complete) => {
progress(100, false)
})
})
}
/**
* Constraint image size
* @param {file} file
* @param {number} maxWidth
* @param {number} maxHeight
*/
const constraintImage = (file,fileName, maxWidth, maxHeight) => {
// Ensure it's an image
if(file.type.match(/image.*/)) {
// Load the image
let reader = new FileReader()
reader.onload = function (readerEvent) {
let image = new Image()
image.onload = function (imageEvent) {
// Resize the image
let canvas = document.createElement('canvas'),
max_size = 986,// TODO : pull max size from a site config
width = image.width,
height = image.height
if (width > height) {
if (width > max_size) {
height *= max_size / width
width = max_size
}
} else {
if (height > max_size) {
width *= max_size / height
height = max_size
}
}
canvas.width = width
canvas.height = height
canvas.getContext('2d').drawImage(image, 0, 0, width, height)
let dataUrl = canvas.toDataURL()
let resizedImage = dataURLToBlob(dataUrl)
let evt = new CustomEvent('onSendResizedImage', { detail: {resizedImage,fileName} })
window.dispatchEvent(evt)
}
image.src = readerEvent.target.result
}
reader.readAsDataURL(file)
}
}
/**
* Convert data URL to blob
* @param {object} dataURL
*/
const dataURLToBlob = (dataURL) => {
let BASE64_MARKER = 'base64,'
if (dataURL.indexOf(BASE64_MARKER) == -1) {
let parts = dataURL.split(',')
let contentType = parts[0].split(':')[1]
let raw = parts[1]
return new Blob([raw], {type: contentType})
}
let parts = dataURL.split(BASE64_MARKER)
let contentType = parts[0].split(':')[1]
let raw = window.atob(parts[1])
let rawLength = raw.length
let uInt8Array = new Uint8Array(rawLength)
for (let i = 0 ;i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i)
}
return new Blob([uInt8Array], {type: contentType})
}
export default {
dataURLToBlob,
convertImageToCanvas,
getExtension,
constraintImage,
uploadImage
}

View File

@@ -1,29 +0,0 @@
// Get tags from post content
export const detectTags = (content,character) => {
return content.split(" ").filter((word) => {
return (word.slice(0,1) === character)
})
}
export const getContentTags = (content) => {
let newTags = []
let tags = detectTags(content,'#')
tags.forEach((tag)=>{
newTags.push(tag.slice(1))
})
return newTags
}
export const sortObjectsDate = (objects) => {
let sortedObjects = objects
// Sort posts with creation date
sortedObjects.sort((a, b) => {
return parseInt(b.creationDate) - parseInt(a.creationDate)
})
return sortedObjects
}

View File

@@ -1,30 +0,0 @@
// - Import react component
import { storageRef } from 'app/firebaseClient/'
//- Import actions
const isValidEmail = (email) => {
let re = /^(([^<>()\[\]\\.,:\s@"]+(\.[^<>()\[\]\\.,:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return re.test(email)
}
function queryString(name, url = window.location.href) {
name = name.replace(/[[]]/g, "\\$&");
const regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)", "i");
const results = regex.exec(url);
if (!results) {
return null;
}
if (!results[2]) {
return "";
}
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
export default {
isValidEmail,
queryString
}

View File

@@ -1,266 +0,0 @@
// - 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 fullName={fullName} 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)

View File

@@ -1,84 +0,0 @@
// - 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(null,{
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

@@ -1,248 +0,0 @@
// - 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 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, URL,fullPath) => {
this.props.set(URL,fullPath)
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
const {saveImageGallery, progressChange} = this.props
FileAPI.uploadImage(resizedImage, fileName, (percent, status) => {
progressChange(percent,status)
}).then((result) => {
/* Add image to image gallery */
saveImageGallery(result.downloadURL,result.metadata.fullPath)
})
}
/**
* Handle on change file upload
*/
onFileChange = (evt) => {
const extension = FileAPI.getExtension(evt.target.files[0].name)
let fileName = (`${uuid()}.${extension}`)
let image = FileAPI.constraintImage(evt.target.files[0], fileName)
}
/**
* Hide image gallery
*/
close = () => {
this.props.close()
}
imageList = () => {
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.URL,image.fullPath)} />}
>
<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.URL} 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 {
saveImageGallery: (imageURL,imageFullPath) => dispatch(imageGalleryActions.dbSaveImage(imageURL,imageFullPath)),
deleteImage: (id) => dispatch(imageGalleryActions.dbDeleteImage(id)),
progressChange : (percent,status) => dispatch(globalActions.progressChange(percent, status))
}
}
/**
* 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)

View File

@@ -1,2 +0,0 @@
import Master from './Master'
export default Master

View File

@@ -1,55 +0,0 @@
// - 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>
)
}
}

View File

@@ -1,54 +0,0 @@
// - 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

@@ -1,36 +0,0 @@
// - 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>
)
}
}

View File

@@ -1,285 +0,0 @@
// - 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'
// - Import app API
import StringAPI from 'StringAPI'
// - 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 = () => {
const {fullNameInput, emailInput, passwordInput, confirmInput} = this.state
const {register} = this.props
let error = false
// Validate full name
let fullNameCheck = fullNameInput.trim().toLowerCase()
if (fullNameCheck.indexOf('test') > -1
|| fullNameCheck.indexOf('demo') > -1
|| fullNameCheck.indexOf('asd') > -1
|| fullNameCheck.length < 4) {
this.setState({
fullNameInputError: 'Please enter a valid name.'
})
error = true
}
/* Validate email*/
if (!StringAPI.isValidEmail(emailInput)) {
this.setState({
emailInputError: 'Please enter a valid email.'
})
error = true
}
/* Check password */
if (passwordInput === '') {
this.setState({
passwordInputError: 'This field is required.'
})
error = true
}
if (confirmInput === '') {
this.setState({
confirmInputError: 'This field is required.'
})
error = true
}
else if(confirmInput !== passwordInput){
this.setState({
passwordInputError: 'This field sould be equal to confirm password.',
confirmInputError: 'This field sould be equal to password.'
})
error = true
}
if (!error) {
register({
email: emailInput,
password: passwordInput,
fullName: fullNameInput
})
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
const paperStyle = {
minHeight: 500,
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}}>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

@@ -1,295 +0,0 @@
// - 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.
evt.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
fullName={this.props.fullName}
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 || '' : '',
fullName: state.user.info && state.user.info[ownProps.userId] ? state.user.info[ownProps.userId].fullName || '' : ''
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(UserBox)

View File

@@ -1,63 +0,0 @@
// - 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)

View File

@@ -1,72 +0,0 @@
// - Import react components
import { firebaseRef, firebaseAuth } from 'app/firebaseClient/'
import { SocialError } from 'domain/common'
import { Notification } from 'domain/notifications'
import { INotificationService } from 'services/notifications'
/**
* Firbase notification service
*
* @export
* @class NotificationService
* @implements {INotificationService}
*/
export class NotificationService implements INotificationService {
public addNotification: (notification: Notification)
=> Promise<void> = (notification: Notification) => {
return new Promise<void>((resolve,reject) => {
firebaseRef.child(`userNotifies/${notification.notifyRecieverUserId}`)
.push(notification)
.then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public getNotifications: (userId: string)
=> Promise<{ [notifyId: string]: Notification }> = (userId) => {
return new Promise<{ [notifyId: string]: Notification }>((resolve,reject) => {
let notifiesRef: any = firebaseRef.child(`userNotifies/${userId}`)
notifiesRef.on('value', (snapshot: any) => {
let notifies: {[notifyId: string]: Notification} = snapshot.val() || {}
resolve(notifies)
})
})
}
public deleteNotification: (notificationId: string, userId: string)
=> Promise <void> = (notificationId, userId) => {
return new Promise<void>((resolve, reject) => {
let updates: any = {}
updates[`userNotifies/${userId}/${notificationId}`] = null
firebaseRef.update(updates)
.then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public setSeenNotification: (notificationId: string, userId: string, notification: Notification)
=> Promise <void> = (notificationId, userId, notification) => {
return new Promise<void>((resolve, reject) => {
let updates: any = {}
updates[`userNotifies/${userId}/${notificationId}`] = notification
firebaseRef.update(updates)
.then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
}

View File

@@ -1,107 +0,0 @@
/**
* Global state
*
* @export
* @class GlobalState
*/
export class GlobalState {
/**
* Set percent of loading progress and visibility for Master component
*
* @type {{
* percent: number,
* visible: Boolean
* }}
* @memberof IGlobalState
*/
progress: {
percent: number
visible: Boolean
} = {
percent : 0,
visible : false
}
/**
* If loading is enabled {true} or not false
*
* @type {Boolean}
* @memberof IGlobalState
*/
loadingStatus: Boolean = true
/**
* If user date is loaded {true} or not {false}
*
* @type {Boolean}
* @memberof IGlobalState
*/
defaultLoadDataStatus: Boolean = false
/**
* If message popup is open {true} or not {false}
*
* @type {Boolean}
* @memberof IGlobalState
*/
messageOpen: Boolean = false
/**
* The text of popup global message
*
* @type {string}
* @memberof IGlobalState
*/
message: string = ''
/**
* Window size
*
* @type {number}
* @memberof IGlobalState
*/
windowWidth: number = 0
/**
* Window height
*
* @type {number}
* @memberof IGlobalState
*/
windowHeight: number = 0
/**
* The text of website header
*
* @type {string}
* @memberof IGlobalState
*/
headerTitle: string = ''
/**
* Top loading is visible {true} or not {false}
*
* @type {Boolean}
* @memberof IGlobalState
*/
showTopLoading: Boolean = false
/**
* Top loading message queue
*
* @type {number}
* @memberof IGlobalState
*/
topLoadingQueue: number = 0
/**
* Temp date storage
*
* @type {*}
* @memberof IGlobalState
*/
temp: any = {}
}

View File

@@ -1,14 +0,0 @@
let React = require('react')
let ReactDOM = require('react-dom')
let TestUtils = require('react-dom/test-utils')
let expect = require('expect')
let $ = require('jquery')
let {CommentWrite} = require('CommentWrite')
describe('CommentWrite', () => {
it('should exist', () => {
expect(CommentWrite).toExist()
})
})

View File

@@ -26,7 +26,7 @@
"inversify": "^4.3.0", "inversify": "^4.3.0",
"keycode": "^2.1.9", "keycode": "^2.1.9",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"material-ui": "^0.19.3", "material-ui": "^0.19.4",
"moment": "^2.18.1", "moment": "^2.18.1",
"morgan": "^1.8.1", "morgan": "^1.8.1",
"node-env-file": "^0.1.8", "node-env-file": "^0.1.8",
@@ -60,13 +60,16 @@
"@types/lodash": "^4.14.77", "@types/lodash": "^4.14.77",
"@types/material-ui": "^0.18.2", "@types/material-ui": "^0.18.2",
"@types/node": "^8.0.33", "@types/node": "^8.0.33",
"@types/prop-types": "^15.5.2",
"@types/react": "^16.0.10", "@types/react": "^16.0.10",
"@types/react-dom": "^16.0.1", "@types/react-dom": "^16.0.1",
"@types/react-event-listener": "^0.4.4",
"@types/react-redux": "^5.0.10", "@types/react-redux": "^5.0.10",
"@types/react-router-dom": "^4.0.8", "@types/react-router-dom": "^4.0.8",
"@types/react-router-redux": "^5.0.8", "@types/react-router-redux": "^5.0.8",
"@types/react-tap-event-plugin": "0.0.30", "@types/react-tap-event-plugin": "0.0.30",
"@types/redux-logger": "^3.0.4", "@types/redux-logger": "^3.0.4",
"@types/uuid": "^3.4.3",
"@types/webpack": "^3.0.13", "@types/webpack": "^3.0.13",
"babel-core": "^6.24.1", "babel-core": "^6.24.1",
"babel-loader": "^7.1.2", "babel-loader": "^7.1.2",

View File

@@ -1,16 +0,0 @@
var moment = require('moment');
console.log(moment().format());
// January 1st 1970 @ 12:00am -> 0
// January 1st 1970 @ 12:01am -> -60
var now = moment();
console.log('Current timestamp', now.unix());
var timestamp = 1459111648;
var currentMoment = moment.unix(timestamp);
console.log('current moment', currentMoment.format('MMM D, YY @ h:mm a'));
// January 3rd, 2016 @ 12:13 AM
console.log('current moment', currentMoment.format('MMMM Do, YYYY @ h:mm A'));

View File

@@ -1,24 +1,27 @@
// - Import react components // - Import react components
import moment from 'moment' import moment from 'moment'
import { push } from 'react-router-redux' import { push } from 'react-router-redux'
// -Import domain // -Import domain
import { User } from 'domain/users' import { User } from 'core/domain/users'
import { SocialError } from 'domain/common' import { SocialError } from 'core/domain/common'
import { UserRegisterModel } from 'models/users/userRegisterModel'
// - Import action types // - Import action types
import { AuthorizeActionType } from 'constants/authorizeActionType' import { AuthorizeActionType } from 'constants/authorizeActionType'
// - Import services // - Import services
import { IAuthorizeService } from 'services/authorize' import { IAuthorizeService } from 'core/services/authorize'
import { IServiceProvider, ServiceProvide } from 'factories' import { IServiceProvider, ServiceProvide } from 'core/factories'
const serviceProvider: IServiceProvider = new ServiceProvide()
const authorizeService: IAuthorizeService = serviceProvider.createAuthorizeService()
// - Import actions // - Import actions
import * as globalActions from 'actions/globalActions' import * as globalActions from 'actions/globalActions'
const serviceProvider: IServiceProvider = new ServiceProvide()
const authorizeService: IAuthorizeService = serviceProvider.createAuthorizeService()
/* _____________ CRUD DB _____________ */ /* _____________ CRUD DB _____________ */
/** /**
@@ -55,10 +58,15 @@ export const dbLogout = () => {
* *
* @param user for registering * @param user for registering
*/ */
export const dbSignup = (user: User) => { export const dbSignup = (user: UserRegisterModel) => {
return (dispatch: any, getState: any) => { return (dispatch: Function, getState: Function) => {
dispatch(globalActions.showNotificationRequest()) dispatch(globalActions.showNotificationRequest())
return authorizeService.registerUser(user).then((result) => { let newUser = new User()
newUser.email = user.email
newUser.password = user.password
newUser.fullName = user.fullName
return authorizeService.registerUser(newUser).then((result) => {
dispatch(signup({ dispatch(signup({
userId: result.uid, userId: result.uid,
...user ...user
@@ -123,7 +131,7 @@ export const logout = () => {
* User registeration call * User registeration call
* @param user for registering * @param user for registering
*/ */
export const signup = (user: User) => { export const signup = (user: UserRegisterModel) => {
return { return {
type: AuthorizeActionType.SIGNUP, type: AuthorizeActionType.SIGNUP,
payload: { ...user } payload: { ...user }

View File

@@ -1,7 +1,7 @@
// - Import domain // - Import domain
import { User } from 'domain/users' import { User } from 'core/domain/users'
import { Circle, UserFollower } from 'domain/circles' import { Circle, UserFollower } from 'core/domain/circles'
import { SocialError } from 'domain/common' import { SocialError } from 'core/domain/common'
// - Import utility components // - Import utility components
import moment from 'moment' import moment from 'moment'
@@ -15,8 +15,8 @@ import * as postActions from 'actions/postActions'
import * as userActions from 'actions/userActions' import * as userActions from 'actions/userActions'
import * as notifyActions from 'actions/notifyActions' import * as notifyActions from 'actions/notifyActions'
import { IServiceProvider,ServiceProvide } from 'factories' import { IServiceProvider,ServiceProvide } from 'core/factories'
import { ICircleService } from 'services/circles' import { ICircleService } from 'core/services/circles'
const serviceProvider: IServiceProvider = new ServiceProvide() const serviceProvider: IServiceProvider = new ServiceProvide()
const circleService: ICircleService = serviceProvider.createCircleService() const circleService: ICircleService = serviceProvider.createCircleService()
@@ -51,7 +51,7 @@ export let dbAddCircle = (circleName: string) => {
* @param {string} cid is circle identifier * @param {string} cid is circle identifier
* @param {User} userFollowing is the user for following * @param {User} userFollowing is the user for following
*/ */
export let dbAddFollowingUser = (cid: string, userFollowing: User) => { export let dbAddFollowingUser = (cid: string, userFollowing: UserFollower) => {
return (dispatch: any, getState: Function) => { return (dispatch: any, getState: Function) => {
let uid: string = getState().authorize.uid let uid: string = getState().authorize.uid

View File

@@ -2,8 +2,8 @@
import moment from 'moment' import moment from 'moment'
// - Import domain // - Import domain
import { Comment } from 'domain/comments' import { Comment } from 'core/domain/comments'
import { SocialError } from 'domain/common' import { SocialError } from 'core/domain/common'
// - Import action types // - Import action types
import { CommentActionType } from 'constants/commentActionType' import { CommentActionType } from 'constants/commentActionType'
@@ -12,8 +12,8 @@ import { CommentActionType } from 'constants/commentActionType'
import * as globalActions from 'actions/globalActions' import * as globalActions from 'actions/globalActions'
import * as notifyActions from 'actions/notifyActions' import * as notifyActions from 'actions/notifyActions'
import { IServiceProvider, ServiceProvide } from 'factories' import { IServiceProvider, ServiceProvide } from 'core/factories'
import { ICommentService } from 'services/comments' import { ICommentService } from 'core/services/comments'
const serviceProvider: IServiceProvider = new ServiceProvide() const serviceProvider: IServiceProvider = new ServiceProvide()
const commentService: ICommentService = serviceProvider.createCommentService() const commentService: ICommentService = serviceProvider.createCommentService()
@@ -26,7 +26,7 @@ const commentService: ICommentService = serviceProvider.createCommentService()
* @param {object} newComment user comment * @param {object} newComment user comment
* @param {function} callBack will be fired when server responsed * @param {function} callBack will be fired when server responsed
*/ */
export const dbAddComment = (ownerPostUserId: string,newComment: Comment, callBack: Function) => { export const dbAddComment = (ownerPostUserId: string | null,newComment: Comment, callBack: Function) => {
return (dispatch: any, getState: Function) => { return (dispatch: any, getState: Function) => {
dispatch(globalActions.showTopLoading()) dispatch(globalActions.showTopLoading())
@@ -74,14 +74,9 @@ export const dbGetComments = () => {
return (dispatch: any, getState: Function) => { return (dispatch: any, getState: Function) => {
let uid: string = getState().authorize.uid let uid: string = getState().authorize.uid
if (uid) { if (uid) {
return commentService.getComments((comments: {[postId: string]: {[commentId: string]: Comment}}) => {
return commentService.getComments()
.then((comments: {[postId: string]: {[commentId: string]: Comment}}) => {
dispatch(addCommentList(comments)) dispatch(addCommentList(comments))
}) })
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
})
} }
} }
} }
@@ -131,24 +126,20 @@ export const dbUpdateComment = (id: string, postId: string, text: string) => {
* @param {string} id of comment * @param {string} id of comment
* @param {string} postId is the identifier of the post which comment belong to * @param {string} postId is the identifier of the post which comment belong to
*/ */
export const dbDeleteComment = (id: string, postId: string) => { export const dbDeleteComment = (id?: string | null, postId?: string) => {
return (dispatch: any, getState: Function) => { return (dispatch: any, getState: Function) => {
if (id === undefined || id === null) { if (id === undefined || id === null) {
dispatch(globalActions.showErrorMessage('comment id can not be null or undefined')) dispatch(globalActions.showErrorMessage('comment id can not be null or undefined'))
} }
console.log('====================================')
console.log(id,postId)
console.log('====================================')
dispatch(globalActions.showTopLoading()) dispatch(globalActions.showTopLoading())
// Get current user id // Get current user id
let uid: string = getState().authorize.uid let uid: string = getState().authorize.uid
return commentService.deleteComment(id,postId) return commentService.deleteComment(id!,postId!)
.then(() => { .then(() => {
dispatch(deleteComment(id, postId)) dispatch(deleteComment(id!, postId!))
dispatch(globalActions.hideTopLoading()) dispatch(globalActions.hideTopLoading())
}, (error: SocialError) => { }, (error: SocialError) => {

View File

@@ -146,7 +146,7 @@ export const hideTopLoading = () => {
/** /**
* Store temp data * Store temp data
*/ */
export const temp = (data: string) => { export const temp = (data: any) => {
return{ return{
type: GlobalActionType.TEMP, type: GlobalActionType.TEMP,
payload: data payload: data
@@ -154,6 +154,16 @@ export const temp = (data: string) => {
} }
/**
* Clear temp data
*/
export const clearTemp = () => {
return{
type: GlobalActionType.CLEAR_TEMP
}
}
// - Load data for guest // - Load data for guest
export const loadDataGuest = () => { export const loadDataGuest = () => {
// tslint:disable-next-line:no-empty // tslint:disable-next-line:no-empty

View File

@@ -2,8 +2,8 @@
import moment from 'moment' import moment from 'moment'
// - Import domain // - Import domain
import { Image } from 'domain/imageGallery' import { Image } from 'core/domain/imageGallery'
import { SocialError } from 'domain/common' import { SocialError } from 'core/domain/common'
// - Import action types // - Import action types
import { ImageGalleryActionType } from 'constants/imageGalleryActionType' import { ImageGalleryActionType } from 'constants/imageGalleryActionType'
@@ -14,8 +14,8 @@ import * as globalActions from 'actions/globalActions'
// - Import app API // - Import app API
import FileAPI from 'api/FileAPI' import FileAPI from 'api/FileAPI'
import { IServiceProvider, ServiceProvide } from 'factories' import { IServiceProvider, ServiceProvide } from 'core/factories'
import { IImageGalleryService } from 'services/imageGallery' import { IImageGalleryService } from 'core/services/imageGallery'
const serviceProvider: IServiceProvider = new ServiceProvide() const serviceProvider: IServiceProvider = new ServiceProvide()
const imageGalleryService: IImageGalleryService = serviceProvider.createImageGalleryService() const imageGalleryService: IImageGalleryService = serviceProvider.createImageGalleryService()

View File

@@ -2,8 +2,8 @@
import moment from 'moment' import moment from 'moment'
// - Import domain // - Import domain
import { Notification } from 'domain/notifications' import { Notification } from 'core/domain/notifications'
import { SocialError } from 'domain/common' import { SocialError } from 'core/domain/common'
// - Import action types // - Import action types
import { NotificationActionType } from 'constants/notificationActionType' import { NotificationActionType } from 'constants/notificationActionType'
@@ -12,8 +12,8 @@ import { NotificationActionType } from 'constants/notificationActionType'
import * as globalActions from 'actions/globalActions' import * as globalActions from 'actions/globalActions'
import * as userActions from 'actions/userActions' import * as userActions from 'actions/userActions'
import { IServiceProvider, ServiceProvide } from 'factories' import { IServiceProvider, ServiceProvide } from 'core/factories'
import { INotificationService } from 'services/notifications' import { INotificationService } from 'core/services/notifications'
const serviceProvider: IServiceProvider = new ServiceProvide() const serviceProvider: IServiceProvider = new ServiceProvide()
const notificationService: INotificationService = serviceProvider.createNotificationService() const notificationService: INotificationService = serviceProvider.createNotificationService()
@@ -50,18 +50,17 @@ export const dbAddNotification = (newNotify: Notification) => {
* Get all notificaitions from database * Get all notificaitions from database
*/ */
export const dbGetNotifications = () => { export const dbGetNotifications = () => {
return (dispatch: any, getState: Function) => { return (dispatch: Function , getState: Function) => {
let uid: string = getState().authorize.uid let uid: string = getState().authorize.uid
if (uid) { if (uid) {
return notificationService.getNotifications(uid,
return notificationService.getNotifications(uid) (notifications: { [notifyId: string]: Notification} ) => {
.then((notifies: { [notifyId: string]: Notification }) => { Object.keys(notifications).forEach((key => {
Object.keys(notifies).forEach((key => { if (!getState().user.info[notifications[key].notifierUserId]) {
if (!getState().user.info[notifies[key].notifierUserId]) { dispatch(userActions.dbGetUserInfoByUserId(notifications[key].notifierUserId,''))
dispatch(userActions.dbGetUserInfoByUserId(notifies[key].notifierUserId,''))
} }
})) }))
dispatch(addNotifyList(notifies)) dispatch(addNotifyList(notifications))
}) })
} }
} }

View File

@@ -2,8 +2,8 @@
import { Action } from 'redux' import { Action } from 'redux'
// - Import domain // - Import domain
import { Post } from 'domain/posts' import { Post } from 'core/domain/posts'
import { SocialError } from 'domain/common' import { SocialError } from 'core/domain/common'
// - Import utility components // - Import utility components
import moment from 'moment' import moment from 'moment'
@@ -14,8 +14,8 @@ import { PostActionType } from 'constants/postActionType'
// - Import actions // - Import actions
import * as globalActions from 'actions/globalActions' import * as globalActions from 'actions/globalActions'
import { IServiceProvider, ServiceProvide } from 'factories' import { IServiceProvider, ServiceProvide } from 'core/factories'
import { IPostService } from 'services/posts' import { IPostService } from 'core/services/posts'
const serviceProvider: IServiceProvider = new ServiceProvide() const serviceProvider: IServiceProvider = new ServiceProvide()
const postService: IPostService = serviceProvider.createPostService() const postService: IPostService = serviceProvider.createPostService()
@@ -68,7 +68,7 @@ export let dbAddPost = (newPost: any, callBack: Function) => {
* @param {object} newPost * @param {object} newPost
* @param {function} callBack * @param {function} callBack
*/ */
export const dbAddImagePost = (newPost: any, callBack: Function) => { export const dbAddImagePost = (newPost: Post, callBack: Function) => {
return (dispatch: any, getState: Function) => { return (dispatch: any, getState: Function) => {
dispatch(globalActions.showTopLoading()) dispatch(globalActions.showTopLoading())
@@ -82,8 +82,8 @@ export const dbAddImagePost = (newPost: any, callBack: Function) => {
viewCount: 0, viewCount: 0,
body: newPost.body, body: newPost.body,
ownerUserId: uid, ownerUserId: uid,
ownerDisplayName: newPost.name, ownerDisplayName: newPost.ownerDisplayName,
ownerAvatar: newPost.avatar, ownerAvatar: newPost.ownerAvatar,
lastEditDate: 0, lastEditDate: 0,
tags: newPost.tags || [], tags: newPost.tags || [],
commentCounter: 0, commentCounter: 0,
@@ -115,7 +115,7 @@ export const dbAddImagePost = (newPost: any, callBack: Function) => {
* @param {object} newPost * @param {object} newPost
* @param {func} callBack //TODO: anti pattern should change to parent state or move state to redux * @param {func} callBack //TODO: anti pattern should change to parent state or move state to redux
*/ */
export const dbUpdatePost = (newPost: any, callBack: Function) => { export const dbUpdatePost = (newPost: Post, callBack: Function) => {
console.log(newPost) console.log(newPost)
return (dispatch: any, getState: Function) => { return (dispatch: any, getState: Function) => {
@@ -126,7 +126,7 @@ export const dbUpdatePost = (newPost: any, callBack: Function) => {
// Write the new data simultaneously in the list // Write the new data simultaneously in the list
let updates: any = {} let updates: any = {}
let post: Post = getState().post.userPosts[uid][newPost.id] let post: Post = getState().post.userPosts[uid][newPost.id!]
let updatedPost: Post = { let updatedPost: Post = {
postTypeId: post.postTypeId, postTypeId: post.postTypeId,
creationDate: post.creationDate, creationDate: post.creationDate,

View File

@@ -1,8 +1,8 @@
// - Import react components // - Import react components
// - Import domain // - Import domain
import { Profile } from 'domain/users' import { Profile } from 'core/domain/users'
import { SocialError } from 'domain/common' import { SocialError } from 'core/domain/common'
// - Import action types // - Import action types
import { UserActionType } from 'constants/userActionType' import { UserActionType } from 'constants/userActionType'
@@ -11,8 +11,8 @@ import { UserActionType } from 'constants/userActionType'
import * as globalActions from 'actions/globalActions' import * as globalActions from 'actions/globalActions'
import * as userActions from 'actions/userActions' import * as userActions from 'actions/userActions'
import { IServiceProvider, ServiceProvide } from 'factories' import { IServiceProvider, ServiceProvide } from 'core/factories'
import { IUserService } from 'services/users' import { IUserService } from 'core/services/users'
const serviceProvider: IServiceProvider = new ServiceProvide() const serviceProvider: IServiceProvider = new ServiceProvide()
const userService: IUserService = serviceProvider.createUserService() const userService: IUserService = serviceProvider.createUserService()
@@ -48,9 +48,14 @@ export const dbGetUserInfo = () => {
* @param {string} callerKey * @param {string} callerKey
*/ */
export const dbGetUserInfoByUserId = (uid: string, callerKey: string) => { export const dbGetUserInfoByUserId = (uid: string, callerKey: string) => {
return (dispatch: any, getState: Function) => { return (dispatch: Function, getState: Function) => {
if (uid) { if (uid) {
let caller = getState().global.temp.caller
if ( caller && caller.indexOf(`dbGetUserInfoByUserId-${uid}`) > -1) {
return
}
dispatch(globalActions.temp({caller: `dbGetUserInfoByUserId-${uid}`}))
return userService.getUserProfile(uid).then((userProfile: Profile) => { return userService.getUserProfile(uid).then((userProfile: Profile) => {
dispatch(addUserInfo(uid, { dispatch(addUserInfo(uid, {
@@ -76,7 +81,6 @@ export const dbGetUserInfoByUserId = (uid: string, callerKey: string) => {
} }
} }
} }
/** /**
* Updata user information * Updata user information
* @param {object} newInfo * @param {object} newInfo

View File

@@ -4,14 +4,14 @@ import moment from 'moment'
import { VoteActionType } from 'constants/voteActionType' import { VoteActionType } from 'constants/voteActionType'
// - Import domain // - Import domain
import { Vote } from 'domain/votes' import { Vote } from 'core/domain/votes'
// - Import actions // - Import actions
import * as globalActions from 'actions/globalActions' import * as globalActions from 'actions/globalActions'
import * as notifyActions from 'actions/notifyActions' import * as notifyActions from 'actions/notifyActions'
import { IServiceProvider, ServiceProvide } from 'factories' import { IServiceProvider, ServiceProvide } from 'core/factories'
import { IVoteService } from 'services/votes' import { IVoteService } from 'core/services/votes'
const serviceProvider: IServiceProvider = new ServiceProvide() const serviceProvider: IServiceProvider = new ServiceProvide()
const voteService: IVoteService = serviceProvider.createVoteService() const voteService: IVoteService = serviceProvider.createVoteService()

47
src/api/CircleAPI.ts Normal file
View File

@@ -0,0 +1,47 @@
import { Circle, UserFollower } from 'core/domain/circles'
/**
* Get the circles' id which the specify users is in that circle
* @param {object} circles
* @param {string} followingId
*/
export const getUserBelongCircles = (circles: {[circleId: string]: Circle},followingId: string) => {
let userBelongCircles: string[] = []
Object.keys(circles).forEach((cid) => {
if (cid.trim() !== '-Followers' && circles[cid].users) {
let isExist = Object.keys(circles[cid].users).indexOf(followingId) > -1
if (isExist) {
userBelongCircles.push(cid)
}
}
})
return userBelongCircles
}
/**
* Get the following users
* @param {object} circles
*/
export const getFollowingUsers = (circles: {[circleId: string]: Circle}) => {
let followingUsers: {[userId: string]: UserFollower} = {}
Object.keys(circles).forEach((cid) => {
if (cid.trim() !== '-Followers' && circles[cid].users) {
Object.keys(circles[cid].users).forEach((userId) => {
let isExist = Object.keys(followingUsers).indexOf(userId) > -1
if (!isExist) {
followingUsers[userId] = {
...circles[cid].users[userId]
}
}
})
}
})
return followingUsers
}
export default {
getUserBelongCircles,
getFollowingUsers
}

147
src/api/FileAPI.ts Normal file
View File

@@ -0,0 +1,147 @@
// - Import react component
import { storageRef } from 'data/firebaseClient'
// - Interface declaration
interface FileReaderEventTarget extends EventTarget {
result: string
}
interface FileReaderEvent extends Event {
target: FileReaderEventTarget
getMessage (): string
}
// - Get file Extension
const getExtension = (fileName: string) => {
let re: RegExp = /(?:\.([^.]+))?$/
return re.exec(fileName)![1]
}
// Converts image to canvas returns new canvas element
const convertImageToCanvas = (image: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap) => {
let canvas = document.createElement('canvas')
canvas.width = image.width
canvas.height = image.height
canvas.getContext('2d')!.drawImage(image, 0, 0)
return canvas
}
/**
* Upload image on the server
* @param {file} file
* @param {string} fileName
*/
const uploadImage = (file: any, fileName: string, progress: (percentage: number, status: boolean) => void) => {
return new Promise<any>((resolve, reject) => {
// Create a storage refrence
let storegeFile = storageRef.child(`images/${fileName}`)
// Upload file
let task = storegeFile.put(file)
task.then((result) => {
resolve(result)
}).catch((error) => {
reject(error)
})
// Upload storage bar
task.on('state_changed', (snapshot: any) => {
let percentage: number = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
progress(percentage, true)
}, (error) => {
console.log('========== Upload Image ============')
console.log(error)
console.log('====================================')
}, () => {
progress(100, false)
})
})
}
/**
* Constraint image size
* @param {file} file
* @param {number} maxWidth
* @param {number} maxHeight
*/
const constraintImage = (file: File,fileName: string, maxWidth?: number, maxHeight?: number) => {
// Ensure it's an image
if (file.type.match(/image.*/)) {
// Load the image
let reader = new FileReader()
reader.onload = function (readerEvent: FileReaderEvent) {
let image = new Image()
image.onload = function (imageEvent: Event) {
// Resize the image
let canvas: HTMLCanvasElement = document.createElement('canvas')
let maxSize: number = 986// TODO : pull max size from a site config
let width: number = image.width
let height: number = image.height
if (width > height) {
if (width > maxSize) {
height *= maxSize / width
width = maxSize
}
} else {
if (height > maxSize) {
width *= maxSize / height
height = maxSize
}
}
canvas.width = width
canvas.height = height
canvas.getContext('2d')!.drawImage(image, 0, 0, width, height)
let dataUrl = canvas.toDataURL()
let resizedImage = dataURLToBlob(dataUrl)
let evt = new CustomEvent('onSendResizedImage', { detail: {resizedImage,fileName} })
window.dispatchEvent(evt)
}
image.src = readerEvent.target.result
}
reader.readAsDataURL(file)
}
}
/**
* Convert data URL to blob
* @param {object} dataURL
*/
const dataURLToBlob = (dataURL: string) => {
let BASE64_MARKER = 'base64,'
if (dataURL.indexOf(BASE64_MARKER) === -1) {
let parts = dataURL.split(',')
let contentType = parts[0].split(':')[1]
let raw = parts[1]
return new Blob([raw], {type: contentType})
}
let parts = dataURL.split(BASE64_MARKER)
let contentType = parts[0].split(':')[1]
let raw = window.atob(parts[1])
let rawLength = raw.length
let uInt8Array = new Uint8Array(rawLength)
for (let i = 0 ;i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i)
}
return new Blob([uInt8Array], {type: contentType})
}
export default {
dataURLToBlob,
convertImageToCanvas,
getExtension,
constraintImage,
uploadImage
}

29
src/api/PostAPI.ts Normal file
View File

@@ -0,0 +1,29 @@
// Get tags from post content
export const detectTags: (content: string, character: string) => string[] = (content: string, character: string) => {
return content.split(' ').filter((word) => {
return (word.slice(0,1) === character)
})
}
export const getContentTags = (content: string) => {
let newTags: string[] = []
let tags = detectTags(content,'#')
tags.forEach((tag) => {
newTags.push(tag.slice(1))
})
return newTags
}
export const sortObjectsDate = (objects: any) => {
let sortedObjects = objects
// Sort posts with creation date
sortedObjects.sort((a: any, b: any) => {
return parseInt(b.creationDate,10) - parseInt(a.creationDate,10)
})
return sortedObjects
}

30
src/api/StringAPI.ts Normal file
View File

@@ -0,0 +1,30 @@
// - Import react component
import { storageRef } from 'data/firebaseClient'
// - Import actions
const isValidEmail = (email: string) => {
let re = /^(([^<>()\[\]\\.,:\s@"]+(\.[^<>()\[\]\\.,:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
return re.test(email)
}
function queryString (name: string, url: string = window.location.href) {
name = name.replace(/[[]]/g, '\\$&')
const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)', 'i')
const results = regex.exec(url)
if (!results) {
return null
}
if (!results[2]) {
return ''
}
return decodeURIComponent(results[2].replace(/\+/g, ' '))
}
export default {
isValidEmail,
queryString
}

View File

@@ -0,0 +1,270 @@
// - 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 'layouts/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 'components/userAvatar'
// - Import API
// - Import actions
import * as circleActions from 'actions/circleActions'
import { ICircleComponentProps } from './ICircleComponentProps'
import { ICircleComponentState } from './ICircleComponentState'
import { Circle } from 'core/domain/circles'
import { Profile } from 'core/domain/users/profile'
/**
* Create component class
*/
export class CircleComponent extends Component<ICircleComponentProps, ICircleComponentState> {
styles = {
userListItem: {
backgroundColor: '#e2e2e2'
},
rightIconMenu: {
display: 'block',
position: 'absolute',
top: '0px',
right: '12px'
},
settingOverlay: {
background: 'rgba(0,0,0,0.12)'
},
settingContent: {
maxWidth: '400px'
}
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor (props: ICircleComponentProps) {
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 CircleComponent
*/
handleChangeCircleName = (evt: any) => {
const { value } = evt.target
this.setState({
circleName: value,
disabledSave: (!value || value.trim() === '')
})
}
/**
* Update user's circle
*
*
* @memberof CircleComponent
*/
handleUpdateCircle = () => {
const { circleName } = this.state
if (circleName && circleName.trim() !== '') {
this.props.updateCircle!({ name: circleName, id: this.props.id })
}
}
/**
* Handle delete circle
*
*
* @memberof CircleComponent
*/
handleDeleteCircle = () => {
this.props.deleteCircle!(this.props.id)
}
/**
* Toggle circle to close/open
*
*
* @memberof CircleComponent
*/
handleToggleCircle = () => {
this.setState({
open: !this.state.open
})
}
userList = () => {
const { users } = this.props.circle
const { userInfo } = this.props
let usersParsed: any = []
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={this.styles.userListItem as any}
value={2}
primaryText={fullName}
leftAvatar={<UserAvatar fullName={fullName} fileName={avatar as any} />}
onClick={() => this.props.goTo!(`/${key}`)}
/>)
})
return usersParsed
}
}
/**
* Right icon menue of circle
*
*
* @memberof CircleComponent
*/
// tslint:disable-next-line:member-ordering
rightIconMenu: any = (
<IconMenu iconButtonElement={IconButtonElement} style={this.styles.rightIconMenu as any}>
<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
key={this.props.id}
title={circleTitle}
modal={false}
open={this.props.openSetting!}
onRequestClose={this.props.closeCircleSettings}
overlayStyle={this.styles.settingOverlay as any}
contentStyle={this.styles.settingContent as any}
>
<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: any, ownProps: ICircleComponentProps) => {
let { uid } = ownProps
return {
deleteCircle: (id: string) => dispatch(circleActions.dbDeleteCircle(id)),
updateCircle: (circle: Circle) => dispatch(circleActions.dbUpdateCircle(circle)),
closeCircleSettings: () => dispatch(circleActions.closeCircleSettings(uid, ownProps.id)),
openCircleSettings: () => dispatch(circleActions.openCircleSettings(uid, ownProps.id)),
goTo: (url: string) => 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: any, ownProps: ICircleComponentProps) => {
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)(CircleComponent as any)

View File

@@ -0,0 +1,84 @@
import { Comment } from 'core/domain/comments'
import { Profile } from 'core/domain/users'
import { Circle } from 'core/domain/circles'
export interface ICircleComponentProps {
/**
*
*
* @type {Circle}
* @memberof ICircleComponentProps
*/
circle: Circle
/**
* Circle identifier
*
* @type {string}
* @memberof ICircleComponentProps
*/
id: string
/**
* User identifier
*
* @type {string}
* @memberof ICircleComponentProps
*/
uid: string
/**
*
*
* @type {Function}
* @memberof ICircleComponentProps
*/
updateCircle?: Function
/**
*
*
* @type {Function}
* @memberof ICircleComponentProps
*/
deleteCircle?: Function
/**
* Users profile
*
* @type {{[userId: string]: Profile}}
* @memberof ICircleComponentProps
*/
userInfo?: {[userId: string]: Profile}
/**
* Close setting box of circle
*
* @type {Function}
* @memberof ICircleComponentProps
*/
closeCircleSettings?: any
/**
* Circle setting dialog is open {true} or not {false}
*
* @type {Function}
* @memberof ICircleComponentProps
*/
openSetting?: boolean
/**
* Change route location
*
* @memberof ICircleComponentProps
*/
goTo?: (url: string) => void
/**
* Open setting box for a circle
*
* @memberof ICircleComponentProps
*/
openCircleSettings?: () => any
}

View File

@@ -0,0 +1,27 @@
export interface ICircleComponentState {
/**
* Circle name
*
* @type {string}
* @memberof ICircleComponentState
*/
circleName: string
/**
* If circle user list is open {true} or not {false}
*
* @type {boolean}
* @memberof ICircleComponentState
*/
open: boolean
/**
* Save button is disabled {true} or not false
*
* @type {boolean}
* @memberof ICircleComponentState
*/
disabledSave: boolean
}

View File

@@ -0,0 +1,2 @@
import CircleComponent from './CircleComponent'
export default CircleComponent

View File

@@ -1,13 +1,11 @@
// - Import react components // - Import react components
import React, { Component } from 'react' import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { NavLink} from 'react-router-dom' import { NavLink } from 'react-router-dom'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { createAction as action } from 'redux-actions'
import moment from 'moment' import moment from 'moment'
import Linkify from 'react-linkify' import Linkify from 'react-linkify'
// - Import material UI libraries // - Import material UI libraries
import { List, ListItem } from 'material-ui/List' import { List, ListItem } from 'material-ui/List'
import Divider from 'material-ui/Divider' import Divider from 'material-ui/Divider'
@@ -21,27 +19,24 @@ import MenuItem from 'material-ui/MenuItem'
import TextField from 'material-ui/TextField' import TextField from 'material-ui/TextField'
// - Import app components // - Import app components
import UserAvatar from 'UserAvatar' import UserAvatarComponent from 'components/userAvatar'
// - Import API // - Import API
// - Import action types // - Import action types
import * as types from 'actionTypes' import * as types from 'constants/actionTypes'
// - Import actions // - Import actions
import * as commentActions from 'commentActions' import * as commentActions from 'actions/commentActions'
import * as userActions from 'userActions' import * as userActions from 'actions/userActions'
import { ICommentComponentProps } from './ICommentComponentProps'
import { ICommentComponentState } from './ICommentComponentState'
/** /**
* Create component class * Create component class
*/ */
export class Comment extends Component { export class CommentComponent extends Component<ICommentComponentProps,ICommentComponentState> {
static propTypes = { static propTypes = {
/** /**
@@ -55,20 +50,84 @@ export class Comment extends Component {
/** /**
* If it's true the comment is disable to write * If it's true the comment is disable to write
*/ */
disableComments: PropTypes.bool, disableComments: PropTypes.bool
} }
/**
* DOM styles
*
*
* @memberof CommentComponent
*/
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'
},
rightIconMenuItem: {
fontSize: '14px'
},
textarea: {
fontWeight: 100,
fontSize: '14px',
border: 'none',
width: '100%',
outline: 'none',
resize: 'none'
},
cancel: {
float: 'right',
clear: 'both',
zIndex: 5,
margin: '0px 5px 5px 0px',
fontWeight: 400
}
}
/**
* Fields
*
* @type {*}
* @memberof CommentComponent
*/
textareaRef: any
divCommentRef: any
inputText: any
divComment: any
/** /**
* Component constructor * Component constructor
* @param {object} props is an object properties of component * @param {object} props is an object properties of component
*/ */
constructor(props) { constructor (props: ICommentComponentProps) {
super(props) super(props)
this.textareaRef = i => { this.inputText = i } this.textareaRef = (i: any) => { this.inputText = i }
this.divCommentRef = i => { this.divComment = i } this.divCommentRef = (i: any) => { this.divComment = i }
//Defaul state // Defaul state
this.state = { this.state = {
/** /**
* Comment text * Comment text
@@ -85,7 +144,7 @@ export class Comment extends Component {
/** /**
* If it's true the post owner is the logged in user which this post be long to the comment * If it's true the post owner is the logged in user which this post be long to the comment
*/ */
isPostOwner: PropTypes.bool isPostOwner: false
} }
@@ -98,22 +157,20 @@ export class Comment extends Component {
} }
/** /**
* Handle show edit comment * Handle show edit comment
* @param {event} evt is an event passed by clicking on edit button * @param {event} evt is an event passed by clicking on edit button
*/ */
handleEditComment = (evt) => { handleEditComment = (evt: any) => {
this.inputText.style.height = this.divComment.clientHeight + 'px' this.inputText.style.height = this.divComment.clientHeight + 'px'
this.props.openEditor() this.props.openEditor()
} }
/** /**
* Handle cancel edit * Handle cancel edit
* @param {event} evt is an event passed by clicking on cancel button * @param {event} evt is an event passed by clicking on cancel button
*/ */
handleCancelEdit = (evt) => { handleCancelEdit = (evt: any) => {
this.setState({ this.setState({
text: this.state.initialText text: this.state.initialText
@@ -125,7 +182,7 @@ export class Comment extends Component {
* Handle edit comment * Handle edit comment
* @param {event} evt is an event passed by clicking on post button * @param {event} evt is an event passed by clicking on post button
*/ */
handleUpdateComment = (evt) => { handleUpdateComment = (evt: any) => {
this.props.update(this.props.comment.id, this.props.comment.postId, this.state.text) this.props.update(this.props.comment.id, this.props.comment.postId, this.state.text)
this.setState({ this.setState({
@@ -134,14 +191,12 @@ export class Comment extends Component {
} }
/** /**
* When comment text changed * When comment text changed
* @param {event} evt is an event passed by change comment text callback funciton * @param {event} evt is an event passed by change comment text callback funciton
* @param {string} data is the comment text which user writes * @param {string} data is the comment text which user writes
*/ */
handleOnChange = (evt) => { handleOnChange = (evt: any) => {
const data = evt.target.value const data = evt.target.value
this.inputText.style.height = evt.target.scrollHeight + 'px' this.inputText.style.height = evt.target.scrollHeight + 'px'
if (data.length === 0 || data.trim() === '' || data.trim() === this.state.initialText) { if (data.length === 0 || data.trim() === '' || data.trim() === this.state.initialText) {
@@ -149,8 +204,7 @@ export class Comment extends Component {
text: data, text: data,
editDisabled: true editDisabled: true
}) })
} } else {
else {
this.setState({ this.setState({
text: data, text: data,
editDisabled: false editDisabled: false
@@ -159,119 +213,81 @@ export class Comment extends Component {
} }
/** /**
* Delete a comment * Delete a comment
* @param {event} evt an event passed by click on delete comment * @param {event} evt an event passed by click on delete comment
* @param {string} id comment identifire * @param {string} id comment identifire
* @param {string} postId post identifier which comment belong to * @param {string} postId post identifier which comment belong to
*/ */
handleDelete = (evt, id, postId) => { handleDelete = (evt: any, id?: string| null, postId?: string) => {
this.props.delete(id, postId) this.props.delete(id, postId)
} }
componentWillMount() { componentWillMount () {
const {userId} = this.props.comment const {userId} = this.props.comment
if (!this.props.isCommentOwner && !this.props.info[userId]) { if (!this.props.isCommentOwner && !this.props.info[userId!]) {
this.props.getUserInfo() this.props.getUserInfo()
} }
} }
/** /**
* Reneder component DOM * Reneder component DOM
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render() { 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 * Comment object from props
*/ */
const {comment} = this.props const {comment} = this.props
const iconButtonElement = ( const iconButtonElement = (
<IconButton style={styles.iconButton} iconStyle={styles.iconButton} <IconButton style={this.styles.iconButton} iconStyle={this.styles.iconButton}
touch={true} touch={true}
> >
<MoreVertIcon color={grey400} viewBox='9 0 24 24' /> <MoreVertIcon color={grey400} viewBox='9 0 24 24' />
</IconButton> </IconButton>
) )
const RightIconMenu = () => ( const RightIconMenu = () => (
<IconMenu iconButtonElement={iconButtonElement} style={{ display: "block", position: "absolute", top: "0px", right: "4px" }}> <IconMenu iconButtonElement={iconButtonElement} style={{ display: 'block', position: 'absolute', top: '0px', right: '4px' }}>
<MenuItem style={{ fontSize: "14px" }}>Reply</MenuItem> <MenuItem style={{ fontSize: '14px' }}>Reply</MenuItem>
{this.props.isCommentOwner ? (<MenuItem style={{ fontSize: "14px" }} onClick={this.handleEditComment}>Edit</MenuItem>): ''} {this.props.isCommentOwner ? (<MenuItem style={this.styles.rightIconMenuItem} 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>): ''} {(this.props.isCommentOwner || this.props.isPostOwner) ? ( <MenuItem style={{ fontSize: '14px' }} onClick={(evt) => this.handleDelete(evt, comment.id, comment.postId)}>Delete</MenuItem>) : ''}
</IconMenu> </IconMenu>
) )
const Author = () => ( const Author = () => (
<div style={{ marginTop: "-11px" }}> <div style={{ marginTop: '-11px' }}>
<span style={styles.author}>{comment.userDisplayName}</span><span style={{ <span style={this.styles.author as any}>{comment.userDisplayName}</span><span style={{
fontWeight: 100, fontWeight: 100,
fontSize: "10px" fontSize: '10px'
}}>{moment.unix(comment.creationDate).fromNow()}</span> }}>{moment.unix(comment.creationDate!).fromNow()}</span>
</div> </div>
) )
const commentBody = ( const commentBody = (
<p style={styles.commentBody}>{comment.text}</p> <p style={this.styles.commentBody as any}>{comment.text}</p>
) )
const {userId} = comment const {userId} = comment
return ( return (
<div className="animate-top" style={styles.comment} key={comment.id}> <div className='animate-top' style={this.styles.comment} key={comment.id!}>
<Paper zDepth={0} className="animate2-top10" style={{ position: "relative", padding: "", display: (!this.state.display ? "block" : "none") }}> <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" }}> <div style={{ marginLeft: '0px', padding: '16px 56px 0px 72px', position: 'relative' }}>
<NavLink to={`/${userId}`}><UserAvatar fullName={this.props.fullName} fileName={this.props.avatar} style={{ display: "inline-flex", alignItems: "center", justifyContent: "center", position: "absolute", top: "8px", left: "16px" }} size={36} /></NavLink> <NavLink to={`/${userId}`}><UserAvatarComponent fullName={this.props.fullName} 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> <NavLink to={`/${userId}`}> <Author /></NavLink>
{(!this.props.isCommentOwner && !this.props.isPostOwner && this.props.disableComments )? '' : (<RightIconMenu />)} {(!this.props.isCommentOwner && !this.props.isPostOwner && this.props.disableComments ) ? '' : (<RightIconMenu />)}
<div style={{ outline: "none", marginLeft: "16px", flex: "auto", flexGrow: 1 }}> <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> <textarea ref={this.textareaRef} className='animate2-top10' style={ Object.assign({}, this.styles.textarea, { display: (this.props.comment.editorStatus ? 'block' : 'none') })} onChange={this.handleOnChange} value={this.state.text!}></textarea>
<Linkify properties={{target: '_blank', style: {color: 'blue'}}}> <Linkify properties={{target: '_blank', style: {color: 'blue'}}}>
<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 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>
</Linkify> </Linkify>
</div> </div>
</div> </div>
<div style={{ display: (this.props.comment.editorStatus ? "flex" : "none"), flexDirection: "row-reverse" }}> <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} 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} /> <FlatButton primary={true} label='Cancel' style={this.styles.cancel as any} onClick={this.handleCancelEdit} />
</div> </div>
</Paper> </Paper>
@@ -280,20 +296,19 @@ export class Comment extends Component {
} }
} }
/** /**
* Map dispatch to props * Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers * @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapDispatchToProps = (dispatch, ownProps) => { const mapDispatchToProps = (dispatch: any, ownProps: any) => {
return { return {
delete: (id, postId) => dispatch(commentActions.dbDeleteComment(id, postId)), delete: (id: string| null, postId: string) => dispatch(commentActions.dbDeleteComment(id, postId)),
update: (id, postId, comment) => dispatch(commentActions.dbUpdateComment(id, postId, comment)), update: (id: string, postId: string, comment: string) => dispatch(commentActions.dbUpdateComment(id, postId, comment)),
openEditor: () => dispatch(commentActions.openCommentEditor({ id: ownProps.comment.id, postId: ownProps.comment.postId })), openEditor: () => dispatch(commentActions.openCommentEditor({ id: ownProps.comment.id, postId: ownProps.comment.postId })),
closeEditor: () => dispatch(commentActions.closeCommentEditor({ 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)) getUserInfo: () => dispatch(userActions.dbGetUserInfoByUserId(ownProps.comment.userId,''))
} }
} }
@@ -303,7 +318,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state: any, ownProps: any) => {
const {uid} = state.authorize const {uid} = state.authorize
const avatar = state.user.info && state.user.info[ownProps.comment.userId] ? state.user.info[ownProps.comment.userId].avatar || '' : '' const avatar = state.user.info && state.user.info[ownProps.comment.userId] ? state.user.info[ownProps.comment.userId].avatar || '' : ''
const fullName = state.user.info && state.user.info[ownProps.comment.userId] ? state.user.info[ownProps.comment.userId].fullName || '' : '' const fullName = state.user.info && state.user.info[ownProps.comment.userId] ? state.user.info[ownProps.comment.userId].fullName || '' : ''
@@ -313,10 +328,8 @@ const mapStateToProps = (state, ownProps) => {
info: state.user.info, info: state.user.info,
avatar, avatar,
fullName fullName
} }
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(Comment) export default connect(mapStateToProps, mapDispatchToProps)(CommentComponent as any)

View File

@@ -0,0 +1,98 @@
import { Comment } from 'core/domain/comments'
import { Profile } from 'core/domain/users'
export interface ICommentComponentProps {
/**
* Comment
*
* @type {Comment}
* @memberof ICommentComponentProps
*/
comment: Comment
/**
* Open profile editor
*
* @type {Function}
* @memberof ICommentComponentProps
*/
openEditor: Function
/**
* Close comment editor
*
* @type {Function}
* @memberof ICommentComponentProps
*/
closeEditor: () => any
/**
* Current user is comment owner {true} or not {false}
*
* @type {boolean}
* @memberof ICommentComponentProps
*/
isCommentOwner: boolean
/**
* Current user is post owner {true} or not {false}
*
* @type {boolean}
* @memberof ICommentComponentProps
*/
isPostOwner: boolean
/**
* Update comment
*
* @memberof ICommentComponentProps
*/
update: (id?: string | null, postId?: string, comment?: string | null) => any
/**
* Delete comment
*
* @memberof ICommentComponentProps
*/
delete: (id?: string| null, postId?: string) => any
/**
* Get user profile
*
* @memberof ICommentComponentProps
*/
getUserInfo: () => void
/**
* User profile
*
* @type {{[userId: string]: Profile}}
* @memberof ICommentComponentProps
*/
info: {[userId: string]: Profile}
/**
* User full name
*
* @type {string}
* @memberof ICommentComponentProps
*/
fullName: string
/**
* User avatar address
*
* @type {string}
* @memberof Comment
*/
avatar: string
/**
* Writing comment on the post is disabled {true} or not false
*
* @type {boolean}
* @memberof ICommentComponentProps
*/
disableComments: boolean
}

View File

@@ -0,0 +1,43 @@
export interface ICommentComponentState {
/**
* Initialt text comment
*
* @type {string}
* @memberof ICommentComponentProps
*/
initialText?: string | null
/**
* Initialt text comment
*
* @type {string}
* @memberof ICommentComponentProps
*/
text?: string | null
/**
* Comment is in edit state {true} or not {false}
*
* @type {boolean}
* @memberof ICommentComponentState
*/
editDisabled: boolean
/**
* Current user is the post owner {true} or not falses
*
* @type {boolean}
* @memberof ICommentComponentState
*/
isPostOwner: boolean
/**
* Display comment {true} or not {false}
*
* @type {boolean}
* @memberof ICommentComponentState
*/
display?: boolean
}

View File

@@ -0,0 +1,2 @@
import CommentComponent from './CommentComponent'
export default CommentComponent

View File

@@ -10,60 +10,76 @@ import { ListItem } from 'material-ui/List'
import { grey400, darkBlack, lightBlack } from 'material-ui/styles/colors' import { grey400, darkBlack, lightBlack } from 'material-ui/styles/colors'
// - Import actions // - Import actions
import * as commentActions from 'commentActions' import * as commentActions from 'actions/commentActions'
// - Import app components // - Import app components
import CommentList from 'CommentList' import CommentListComponent from 'components/CommentList'
import CommentWrite from 'CommentWrite' import UserAvatarComponent from 'components/userAvatar'
import UserAvatar from 'UserAvatar'
import { ICommentGroupComponentProps } from './ICommentGroupComponentProps'
import { ICommentGroupComponentState } from './ICommentGroupComponentState'
import { Comment } from 'core/domain/comments/comment'
/** /**
* Create component class * Create component class
*/ */
export class CommentGroup extends Component { export class CommentGroupComponent extends Component<ICommentGroupComponentProps, ICommentGroupComponentState> {
static propTypes = {
static propTypes = {
/** /**
* If it's true comment box will be open * If it's true comment box will be open
*/ */
open: PropTypes.bool, open: PropTypes.bool,
/** /**
* If it's true the comment is disable to write * If it's true the comment is disable to write
*/ */
disableComments: PropTypes.bool, disableComments: PropTypes.bool,
/** /**
* The post identifier which comment belong to * The post identifier which comment belong to
*/ */
postId: PropTypes.string, postId: PropTypes.string,
/** /**
* If it's true the post owner is the logged in user which this post be long to the comment * If it's true the post owner is the logged in user which this post be long to the comment
*/ */
isPostOwner: PropTypes.bool, isPostOwner: PropTypes.bool,
/** /**
* Toggle on show/hide comment by passing callback from parent component * Toggle on show/hide comment by passing callback from parent component
*/ */
onToggleRequest: PropTypes.func, onToggleRequest: PropTypes.func,
/** /**
* The user identifier of the post owner which comment belong to * The user identifier of the post owner which comment belong to
*/ */
ownerPostUserId:PropTypes.string ownerPostUserId: PropTypes.string
} }
styles = {
commentItem: {
height: '60px',
position: '',
zIndex: ''
},
toggleShowList: {
height: '60px',
zIndex: 5
},
writeCommentTextField: {
width: '100%'
}
}
/** /**
* Component constructor * Component constructor
* @param {object} props is an object properties of component * @param {object} props is an object properties of component
*/ */
constructor(props) { constructor (props: ICommentGroupComponentProps) {
super(props) super(props)
/** /**
* Defaul state * Defaul state
*/ */
this.state = { this.state = {
commentText: "", commentText: '',
postDisable: true postDisable: true
} }
@@ -72,8 +88,6 @@ static propTypes = {
this.commentList = this.commentList.bind(this) this.commentList = this.commentList.bind(this)
this.handlePostComment = this.handlePostComment.bind(this) this.handlePostComment = this.handlePostComment.bind(this)
this.clearCommentWrite = this.clearCommentWrite.bind(this) this.clearCommentWrite = this.clearCommentWrite.bind(this)
} }
/** /**
@@ -90,7 +104,7 @@ static propTypes = {
* Post comment * Post comment
*/ */
handlePostComment = () => { handlePostComment = () => {
this.props.send(this.state.commentText, this.props.postId, this.clearCommentWrite) this.props.send!(this.state.commentText, this.props.postId, this.clearCommentWrite)
} }
/** /**
@@ -98,15 +112,14 @@ static propTypes = {
* @param {event} evt is an event passed by change comment text callback funciton * @param {event} evt is an event passed by change comment text callback funciton
* @param {string} data is the comment text which user writes * @param {string} data is the comment text which user writes
*/ */
handleOnChange = (evt, data) => { handleOnChange = (evt: any, data: any) => {
this.setState({ commentText: data }) this.setState({ commentText: data })
if (data.length === 0 || data.trim() === '') { if (data.length === 0 || data.trim() === '') {
this.setState({ this.setState({
commentText: '', commentText: '',
postDisable: true postDisable: true
}) })
} } else {
else {
this.setState({ this.setState({
commentText: data, commentText: data,
postDisable: false postDisable: false
@@ -123,48 +136,45 @@ static propTypes = {
let comments = this.props.comments let comments = this.props.comments
if (comments) { if (comments) {
let parsedComments: Comment[] = []
let parsedComments = []
Object.keys(comments).slice(0, 3).forEach((commentId) => { Object.keys(comments).slice(0, 3).forEach((commentId) => {
parsedComments.push({ parsedComments.push({
id: commentId, id: commentId,
...comments[commentId] ...comments![commentId]
}) })
}) })
if (parsedComments.length === 2) { if (parsedComments.length === 2) {
parsedComments.push(parsedComments[0]) parsedComments.push(parsedComments[0])
} } else if (parsedComments.length === 1) {
else if (parsedComments.length === 1) {
parsedComments.push(parsedComments[0]) parsedComments.push(parsedComments[0])
parsedComments.push(parsedComments[0]) parsedComments.push(parsedComments[0])
} }
return parsedComments.map((comment, index) => { return parsedComments.map((comment, index) => {
const {userInfo} = this.props const {userInfo} = this.props
const commentAvatar = userInfo && userInfo[comment.userId] ? userInfo[comment.userId].avatar || '' : '' const commentAvatar = userInfo && userInfo[comment.userId!] ? userInfo[comment.userId!].avatar || '' : ''
const commentFullName = userInfo && userInfo[comment.userId] ? userInfo[comment.userId].fullName || '' : '' const commentFullName = userInfo && userInfo[comment.userId!] ? userInfo[comment.userId!].fullName || '' : ''
return (<ListItem key={index} style={{ height: "60px", position: "", zIndex: "" }} innerDivStyle={{ padding: "6px 16px 16px 72px" }} return (<ListItem key={index} style={this.styles.commentItem as any} innerDivStyle={{ padding: '6px 16px 16px 72px' }}
leftAvatar={<UserAvatar fullName={commentFullName} fileName={commentAvatar} style={{ top: "8px" }} size={36} />} leftAvatar={<UserAvatarComponent fullName={commentFullName} fileName={commentAvatar} style={{ top: '8px' }} size={36} />}
secondaryText={<div style={{ height: "" }}> secondaryText={<div style={{ height: '' }}>
<span style={{ <span style={{
fontSize: "13px", fontSize: '13px',
paddingRight: "10px", paddingRight: '10px',
fontWeight: 400, fontWeight: 400,
color: "rgba(0,0,0,0.87)", color: 'rgba(0,0,0,0.87)',
textOverflow: "ellipsis", textOverflow: 'ellipsis',
overflow: "hidden" overflow: 'hidden'
}}> }}>
{comment.userDisplayName}: {comment.userDisplayName}:
</span> </span>
<span style={{ <span style={{
fontSize: "13px", fontSize: '13px',
lineHeight: "20px", lineHeight: '20px',
color: "rgba(0,0,0,0.87)", color: 'rgba(0,0,0,0.87)',
fontWeight: 300, fontWeight: 300,
whiteSpace: "pre-wrap" whiteSpace: 'pre-wrap'
}}>{comment.text}</span> }}>{comment.text}</span>
</div>} </div>}
secondaryTextLines={2} secondaryTextLines={2}
/> />
@@ -180,55 +190,53 @@ static propTypes = {
* Reneder component DOM * Reneder component DOM
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render() { render () {
return ( return (
<div> <div>
<div style={this.props.comments && Object.keys(this.props.comments).length > 0 ? { display: "block" } : { display: "none" }}> <div style={this.props.comments && Object.keys(this.props.comments).length > 0 ? { display: 'block' } : { display: 'none' }}>
<Divider /> <Divider />
<Paper zDepth={0} className="animate-top" style={!this.props.open ? { display: "block" } : { display: "none" }}> <Paper zDepth={0} className='animate-top' style={!this.props.open ? { display: 'block' } : { display: 'none' }}>
<div style={{ position: "relative", height: "60px" }} > <div style={{ position: 'relative', height: '60px' }} >
<FlatButton label=" " style={{ height: "60px", zIndex: 5 }} fullWidth={true} onClick={this.props.onToggleRequest} /> <FlatButton label=' ' style={this.styles.toggleShowList} fullWidth={true} onClick={this.props.onToggleRequest} />
<div className="comment__list-show"> <div className='comment__list-show'>
{this.commentList()} {this.commentList()}
</div> </div>
</div> </div>
</Paper> </Paper>
{(this.props.comments && Object.keys(this.props.comments).length > 0) {(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" }}> ? (<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}/> <CommentListComponent comments={this.props.comments} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/>
</Paper>) : ''} </Paper>) : ''}
</div> </div>
{!this.props.disableComments ? (<div> {!this.props.disableComments ? (<div>
<Divider /> <Divider />
<Paper zDepth={0} className="animate2-top10" style={{ position: "relative", overflowY: "auto", padding: "12px 16px", display: (this.props.open ? "block" : "none") }}> <Paper zDepth={0} className='animate2-top10' style={{ position: 'relative', overflowY: 'auto', padding: '12px 16px', display: (this.props.open ? 'block' : 'none') }}>
<div style={{ display: "flex" }}> <div style={{ display: 'flex' }}>
<UserAvatar fullName={this.props.fullName} fileName={this.props.avatar} style={{ flex: "none", margin: "4px 0px" }} size={36} /> <UserAvatarComponent fullName={this.props.fullName!} fileName={this.props.avatar!} style={{ flex: 'none', margin: '4px 0px' }} size={36} />
<div style={{ outline: "none", marginLeft: "16px", flex: "auto", flexGrow: 1 }}> <div style={{ outline: 'none', marginLeft: '16px', flex: 'auto', flexGrow: 1 }}>
<TextField <TextField
value={this.state.commentText} value={this.state.commentText}
onChange={this.handleOnChange} onChange={this.handleOnChange}
hintText="Add a comment..." hintText='Add a comment...'
underlineShow={false} underlineShow={false}
multiLine={true} multiLine={true}
rows={1} rows={1}
hintStyle={{ fontWeight: 100, fontSize: "14px" }} hintStyle={{ fontWeight: 100, fontSize: '14px' }}
rowsMax={4} rowsMax={4}
textareaStyle={{ fontWeight: 100, fontSize: "14px" }} textareaStyle={{ fontWeight: 100, fontSize: '14px' }}
style={{ width: '100%' }} style={this.styles.writeCommentTextField}
/> />
</div> </div>
</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} /> <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> </Paper>
</div>): ''} </div>) : ''}
</div> </div>
) )
} }
@@ -240,9 +248,9 @@ static propTypes = {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapDispatchToProps = (dispatch, ownProps) => { const mapDispatchToProps = (dispatch: any, ownProps: ICommentGroupComponentProps) => {
return { return {
send: (text, postId, callBack) => { send: (text: string, postId: string, callBack: Function) => {
dispatch(commentActions.dbAddComment(ownProps.ownerPostUserId,{ dispatch(commentActions.dbAddComment(ownProps.ownerPostUserId,{
postId: postId, postId: postId,
text: text text: text
@@ -257,15 +265,15 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state: any, ownProps: ICommentGroupComponentProps) => {
return { return {
comments: state.comment.postComments[ownProps.postId], comments: state.comment.postComments[ownProps.postId],
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar || '' : '', avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar || '' : '',
fullName: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].fullName || '' : '', fullName: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].fullName || '' : '',
userInfo: state.user.info userInfo: state.user.info
} }
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(CommentGroup) export default connect(mapStateToProps, mapDispatchToProps)(CommentGroupComponent as any)

View File

@@ -0,0 +1,93 @@
import { Profile } from 'core/domain/users'
import { Comment } from 'core/domain/comments'
export interface ICommentGroupComponentProps {
/**
* Commnets
*
* @type {{[commentId: string]: Comment}}
* @memberof ICommentGroupComponentProps
*/
comments?: {[commentId: string]: Comment}
/**
* The post identifier which comment belong to
*
* @type {string}
* @memberof ICommentGroupComponentProps
*/
postId: string
/**
* Users` profile
*
* @type {{[userId: string]: Profile}}
* @memberof ICommentGroupComponentProps
*/
userInfo?: {[userId: string]: Profile}
/**
* Comment group is open {true} or not {false}
*
* @type {boolean}
* @memberof ICommentGroupComponentProps
*/
open: boolean
/**
* Comment is disabled {true} or not {false}
*
* @type {boolean}
* @memberof ICommentGroupComponentProps
*/
disableComments: boolean
/**
* Current user is the post owner {true} or not {false}
*
* @type {boolean}
* @memberof ICommentGroupComponentProps
*/
isPostOwner: boolean
/**
* User full name
*
* @type {string}
* @memberof ICommentGroupComponentProps
*/
fullName?: string
/**
* Avatar URL address
*
* @type {string}
* @memberof ICommentGroupComponentProps
*/
avatar?: string
/**
* Toggle comment list open/close
*
* @type {Function}
* @memberof ICommentGroupComponentProps
*/
onToggleRequest: () => void
/**
* The identifier of post owner
*
* @type {string}
* @memberof ICommentGroupComponentProps
*/
ownerPostUserId: string
/**
* Send comment
*
* @type {(commentText: string, postId: string, clearCommentWrite: Function)}
* @memberof ICommentGroupComponentProps
*/
send?: (commentText: string, postId: string, clearCommentWrite: () => void) => any
}

View File

@@ -0,0 +1,19 @@
export interface ICommentGroupComponentState {
/**
* Comment text
*
* @type {string}
* @memberof ICommentGroupComponentState
*/
commentText: string
/**
* Disable post comment
*
* @type {boolean}
* @memberof ICommentGroupComponentState
*/
postDisable: boolean
}

View File

@@ -0,0 +1,2 @@
import CommentGroupComponent from './CommentGroupComponent'
export default CommentGroupComponent

View File

@@ -4,24 +4,23 @@ import { connect } from 'react-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { List, ListItem } from 'material-ui/List' import { List, ListItem } from 'material-ui/List'
// - Import app components // - Import app components
import Comment from 'Comment' import CommentComponent from 'components/Comment'
import * as PostAPI from 'PostAPI'
import * as PostAPI from 'api/PostAPI'
import { ICommentListComponentProps } from './ICommentListComponentProps'
import { ICommentListComponentState } from './ICommentListComponentState'
import { Comment } from 'core/domain/comments'
// - Import actions // - Import actions
/** /**
* Create component class * Create component class
*/ */
export class CommentList extends Component { export class CommentListComponent extends Component<ICommentListComponentProps, ICommentListComponentState> {
static propTypes = { static propTypes = {
/** /**
* If it's true the post owner is the logged in user which this post be long to the comment * If it's true the post owner is the logged in user which this post be long to the comment
*/ */
@@ -29,14 +28,14 @@ export class CommentList extends Component {
/** /**
* If it's true the comment is disable to write * If it's true the comment is disable to write
*/ */
disableComments: PropTypes.bool, disableComments: PropTypes.bool
} }
/** /**
* Component constructor * Component constructor
* @param {object} props is an object properties of component * @param {object} props is an object properties of component
*/ */
constructor(props) { constructor (props: ICommentListComponentProps) {
super(props) super(props)
/** /**
@@ -50,8 +49,6 @@ export class CommentList extends Component {
} }
/** /**
* Get comments' DOM * Get comments' DOM
* @return {DOM} list of comments' DOM * @return {DOM} list of comments' DOM
@@ -60,8 +57,7 @@ export class CommentList extends Component {
let comments = this.props.comments let comments = this.props.comments
if (comments) { if (comments) {
let parsedComments: Comment[] = []
let parsedComments = []
Object.keys(comments).forEach((commentId) => { Object.keys(comments).forEach((commentId) => {
parsedComments.push({ parsedComments.push({
id: commentId, id: commentId,
@@ -69,10 +65,10 @@ export class CommentList extends Component {
}) })
}) })
let sortedComments = PostAPI.sortObjectsDate(parsedComments) 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}/> return sortedComments.map((comment: Comment, index: number, array: Comment) => {
return <CommentComponent key={comment.id} comment={comment} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/>
}) })
@@ -83,11 +79,11 @@ export class CommentList extends Component {
* Reneder component DOM * Reneder component DOM
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render() { render () {
const styles = { const styles: any = {
list: { list: {
width: "100%", width: '100%',
maxHeight: 450, maxHeight: 450,
overflowY: 'auto' overflowY: 'auto'
} }
@@ -95,7 +91,6 @@ export class CommentList extends Component {
return ( return (
<List style={styles.list}> <List style={styles.list}>
{this.commentList()} {this.commentList()}
@@ -110,10 +105,9 @@ export class CommentList extends Component {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapDispatchToProps = (dispatch, ownProps) => { const mapDispatchToProps = (dispatch: any, ownProps: ICommentListComponentProps) => {
return { return {
} }
} }
@@ -123,11 +117,11 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state) => { const mapStateToProps = (state: any) => {
return { return {
} }
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(CommentList) export default connect(mapStateToProps, mapDispatchToProps)(CommentListComponent as any)

View File

@@ -0,0 +1,28 @@
import { Comment } from 'core/domain/comments'
export interface ICommentListComponentProps {
/**
* Ad dictionary of comment
*
* @type {{[commentId: string]: Comment}}
* @memberof ICommentListComponentProps
*/
comments: {[commentId: string]: Comment}
/**
* Current user is post the post owner {true} or not false
*
* @type {boolean}
* @memberof ICommentListComponentProps
*/
isPostOwner: boolean
/**
* Comment on the post is disabled {false} or not {true}
*
* @type {boolean}
* @memberof ICommentListComponentProps
*/
disableComments: boolean
}

View File

@@ -0,0 +1,4 @@
export interface ICommentListComponentState {
}

View File

@@ -0,0 +1,2 @@
import CommentListComponent from './CommentListComponent'
export default CommentListComponent

View File

@@ -1,5 +1,3 @@
// - Import react components // - Import react components
import React, { Component } from 'react' import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
@@ -18,316 +16,296 @@ import Divider from 'material-ui/Divider'
import Paper from 'material-ui/Paper' import Paper from 'material-ui/Paper'
import TextField from 'material-ui/TextField' import TextField from 'material-ui/TextField'
// - Import app components // - Import app components
import ImgCover from 'ImgCover' import ImgCover from 'components/imgCover'
import DialogTitle from 'DialogTitle' import UserAvatarComponent from 'components/userAvatar'
import ImageGallery from 'ImageGallery' import ImageGallery from 'components/imageGallery'
import FileAPI from 'FileAPI' import DialogTitle from 'layouts/DialogTitle'
import UserAvatar from 'UserAvatar'
// - Import API // - Import API
import FileAPI from 'api/FileAPI'
// - Import actions // - Import actions
import * as userActions from 'userActions' import * as userActions from 'actions/userActions'
import * as globalActions from 'globalActions' import * as globalActions from 'actions/globalActions'
import * as imageGalleryActions from 'imageGalleryActions' import * as imageGalleryActions from 'actions/imageGalleryActions'
import { IEditProfileComponentProps } from './IEditProfileComponentProps'
import { IEditProfileComponentState } from './IEditProfileComponentState'
import { Profile } from 'core/domain/users'
/** /**
* Create component class * Create component class
*/ */
export class EditProfile extends Component { export class EditProfileComponent extends Component<IEditProfileComponentProps,IEditProfileComponentState> {
static propTypes = { static propTypes = {
/** /**
* User avatar address * User avatar address
*/ */
avatar: PropTypes.string, avatar: PropTypes.string,
/** /**
* User avatar address * User avatar address
*/ */
banner: PropTypes.string, banner: PropTypes.string,
/** /**
* User full name * User full name
*/ */
fullName: PropTypes.string.isRequired fullName: PropTypes.string.isRequired
}
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'
},
iconButtonSmall: {
},
iconButton: {
} }
}
/** /**
* Component constructor * Component constructor
* @param {object} props is an object properties of component * @param {object} props is an object properties of component
*/ */
constructor(props) { constructor (props: IEditProfileComponentProps) {
super(props) super(props)
//Defaul state // Defaul state
this.state = { this.state = {
/** /**
* If it's true the winow is in small size * If it's true the winow is in small size
*/ */
isSmall: false, isSmall: false,
/** /**
* User tag line input value * User tag line input value
*/ */
tagLineInput: props.info.tagLine || '', tagLineInput: props.info!.tagLine || '',
/** /**
* User full name input value * User full name input value
*/ */
fullNameInput: props.info.fullName || '', fullNameInput: props.info!.fullName || '',
/** /**
* Error message of full name input * Error message of full name input
*/ */
fullNameInputError: '', fullNameInputError: '',
/** /**
* User banner address * User banner address
*/ */
banner: this.props.banner || 'https://firebasestorage.googleapis.com/v0/b/open-social-33d92.appspot.com/o/images%2F751145a1-9488-46fd-a97e-04018665a6d3.JPG?alt=media&token=1a1d5e21-5101-450e-9054-ea4a20e06c57', banner: props.banner || 'https://firebasestorage.googleapis.com/v0/b/open-social-33d92.appspot.com/o/images%2F751145a1-9488-46fd-a97e-04018665a6d3.JPG?alt=media&token=1a1d5e21-5101-450e-9054-ea4a20e06c57',
/** /**
* User avatar address * User avatar address
*/ */
avatar: this.props.avatar || '', avatar: props.avatar || '',
/** /**
* It's true if the image galley for banner is open * It's true if the image galley for banner is open
*/ */
openBanner: false, openBanner: false,
/** /**
* It's true if the image gallery for avatar is open * It's true if the image gallery for avatar is open
*/ */
openAvatar: false openAvatar: false
} }
// Binding functions to `this` // Binding functions to `this`
this.handleChangeDate = this.handleChangeDate.bind(this) this.handleUpdate = this.handleUpdate.bind(this)
this.handleUpdate = this.handleUpdate.bind(this) this.handleRequestSetAvatar = this.handleRequestSetAvatar.bind(this)
this.handleRequestSetAvatar = this.handleRequestSetAvatar.bind(this) this.handleRequestSetBanner = this.handleRequestSetBanner.bind(this)
this.handleRequestSetBanner = this.handleRequestSetBanner.bind(this)
} }
/** /**
* Close image gallery of banner * Close image gallery of banner
*/ */
handleCloseBannerGallery = () => { handleCloseBannerGallery = () => {
this.setState({ this.setState({
openBanner: false openBanner: false
}) })
} }
/** /**
* Open image gallery of banner * Open image gallery of banner
*/ */
handleOpenBannerGallery = () => { handleOpenBannerGallery = () => {
this.setState({ this.setState({
openBanner: true openBanner: true
}) })
} }
/** /**
* Close image gallery of avatar * Close image gallery of avatar
*/ */
handleCloseAvatarGallery = () => { handleCloseAvatarGallery = () => {
this.setState({ this.setState({
openAvatar: false openAvatar: false
}) })
} }
/** /**
* Open image gallery of avatar * Open image gallery of avatar
*/ */
handleOpenAvatarGallery = () => { handleOpenAvatarGallery = () => {
this.setState({ this.setState({
openAvatar: true openAvatar: true
}) })
} }
/**
/**
* Set banner image url * Set banner image url
*/ */
handleRequestSetBanner = (url) => { handleRequestSetBanner = (url: string) => {
console.log('==========Banner==================') this.setState({
console.log(url) banner: url
console.log('====================================') })
this.setState({ }
banner: url
})
}
/** /**
* Set avatar image url * Set avatar image url
*/ */
handleRequestSetAvatar = (fileName) => { handleRequestSetAvatar = (fileName: string) => {
this.setState({ this.setState({
avatar: fileName avatar: fileName
}) })
} }
/** /**
* Update profile on the server * Update profile on the server
* *
* *
* @memberof EditProfile * @memberof EditProfile
*/ */
handleUpdate = () => { handleUpdate = () => {
const {fullNameInput, tagLineInput, avatar, banner} = this.state 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({ if (this.state.fullNameInput.trim() === '') {
fullName: fullNameInput, this.setState({
tagLine: tagLineInput, fullNameInputError: 'This field is required'
avatar: avatar, })
banner: banner } else {
}) this.setState({
} fullNameInputError: ''
})
this.props.update!({
fullName: fullNameInput,
tagLine: tagLineInput,
avatar: avatar,
banner: banner
})
} }
}
/** /**
* Handle data on input change * Handle data on input change
* @param {event} evt is an event of inputs of element on change * @param {event} evt is an event of inputs of element on change
*/ */
handleInputChange = (evt) => { handleInputChange = (event: any) => {
const target = evt.target const target = event.target
const value = target.type === 'checkbox' ? target.checked : target.value const value = target.type === 'checkbox' ? target.checked : target.value
const name = target.name const name = target.name
this.setState({ this.setState({
[name]: value [name]: value
}) })
} }
/**
* Handle change date
*/
handleChangeDate = (evt, date) => {
this.setState({
birthdayInput: date,
})
}
/** /**
* Handle resize event for window to change sidebar status * Handle resize event for window to change sidebar status
* @param {event} evt is the event is passed by winodw resize event * @param {any} event is the event is passed by winodw resize event
*/ */
handleResize = (evt) => { handleResize = (event: any) => {
// Set initial state // Set initial state
let width = window.innerWidth let width = window.innerWidth
if (width > 900) { if (width > 900) {
this.setState({ this.setState({
isSmall: false isSmall: false
}) })
} } else {
else { this.setState({
this.setState({ isSmall: true
isSmall: true })
})
}
}
componentDidMount = () => {
this.handleResize()
} }
}
componentDidMount () {
this.handleResize(null)
}
/** /**
* Reneder component DOM * Reneder component DOM
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render() { render () {
const styles = { const iconButtonElement = (
avatar: { <IconButton style={this.state.isSmall ? this.styles.iconButtonSmall : this.styles.iconButton} iconStyle={this.state.isSmall ? this.styles.iconButtonSmall : this.styles.iconButton}
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} touch={true}
> >
<MoreVertIcon color={grey400} viewBox='10 0 24 24' /> <MoreVertIcon color={grey400} viewBox='10 0 24 24' />
</IconButton> </IconButton>
) )
const RightIconMenu = () => (
const RightIconMenu = () => (
<IconMenu iconButtonElement={iconButtonElement}> <IconMenu iconButtonElement={iconButtonElement}>
<MenuItem style={{ fontSize: "14px" }}>Reply</MenuItem> <MenuItem style={{ fontSize: '14px' }}>Reply</MenuItem>
<MenuItem style={{ fontSize: "14px" }}>Edit</MenuItem> <MenuItem style={{ fontSize: '14px' }}>Edit</MenuItem>
<MenuItem style={{ fontSize: "14px" }}>Delete</MenuItem> <MenuItem style={{ fontSize: '14px' }}>Delete</MenuItem>
</IconMenu> </IconMenu>
) )
return (
return (
<div> <div>
{/* Edit profile dialog */} {/* Edit profile dialog */}
<Dialog <Dialog
id='Edit-Profile' key='Edit-Profile'
modal={false} modal={false}
open={this.props.open} open={this.props.open!}
onRequestClose={this.props.onRequestClose} onRequestClose={this.props.onRequestClose}
autoScrollBodyContent={true} autoScrollBodyContent={true}
bodyStyle={{ backgroundColor: "none", padding: 'none', borderTop: 'none', borderBottom: 'none' }} bodyStyle={{ backgroundColor: 'none', padding: 'none', borderTop: 'none', borderBottom: 'none' }}
overlayStyle={{ background: "rgba(0,0,0,0.12)" }} overlayStyle={{ background: 'rgba(0,0,0,0.12)' }}
contentStyle={{ backgroundColor: "none", maxWidth: '450px', maxHeight: 'none', height: '100%' }} contentStyle={{ backgroundColor: 'none', maxWidth: '450px', maxHeight: 'none', height: '100%' }}
style={{ backgroundColor: "none", maxHeight: 'none', height: '100%' }} style={{ backgroundColor: 'none', maxHeight: 'none', height: '100%' }}
> >
{/* Banner */} {/* Banner */}
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
@@ -338,7 +316,7 @@ export class EditProfile extends Component {
</div> </div>
<div className='profile__edit'> <div className='profile__edit'>
<EventListener <EventListener
target="window" target='window'
onResize={this.handleResize} onResize={this.handleResize}
/> />
<div className='left'> <div className='left'>
@@ -348,25 +326,24 @@ export class EditProfile extends Component {
<SvgCamera style={{ fill: 'rgba(255, 255, 255, 0.88)', transform: 'translate(6px, 6px)' }} /> <SvgCamera style={{ fill: 'rgba(255, 255, 255, 0.88)', transform: 'translate(6px, 6px)' }} />
</div> </div>
<UserAvatar fullName={(this.props.info ? this.props.info.fullName : '')} fileName={this.state.avatar} size={90} style={styles.avatar} /> <UserAvatarComponent fullName={(this.props.info ? this.props.info.fullName : '')} fileName={this.state.avatar} size={90} style={this.styles.avatar} />
</div> </div>
<div className='info'> <div className='info'>
<div className='fullName'> <div className='fullName'>
{this.props.fullName} {this.props.fullName}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{/* Edit user information box*/} {/* Edit user information box*/}
<Paper style={styles.paper} zDepth={1}> <Paper style={this.styles.paper} zDepth={1}>
<div style={styles.title}>Personal Information</div> <div style={this.styles.title as any}>Personal Information</div>
<div style={styles.box}> <div style={this.styles.box}>
<TextField <TextField
floatingLabelText="Full name" floatingLabelText='Full name'
onChange={this.handleInputChange} onChange={this.handleInputChange}
name='fullNameInput' name='fullNameInput'
errorText={this.state.fullNameInputError} errorText={this.state.fullNameInputError}
@@ -374,23 +351,22 @@ export class EditProfile extends Component {
/> />
</div> </div>
<br /> <br />
<div style={styles.box}> <div style={this.styles.box}>
<TextField <TextField
floatingLabelText="Tag Line" floatingLabelText='Tag Line'
onChange={this.handleInputChange} onChange={this.handleInputChange}
name='tagLineInput' name='tagLineInput'
value={this.state.tagLineInput} value={this.state.tagLineInput}
/> />
</div> </div>
<br /> <br />
<div style={styles.actions}> <div style={this.styles.actions as any}>
<FlatButton label="CANCEL" onClick={this.props.onRequestClose} /> <FlatButton label='CANCEL' onClick={this.props.onRequestClose} />
<RaisedButton label="UPDATE" primary={true} onClick={this.handleUpdate} style={styles.updateButton} /> <RaisedButton label='UPDATE' primary={true} onClick={this.handleUpdate} style={this.styles.updateButton} />
</div> </div>
</Paper> </Paper>
<div style={{ height: '16px' }}></div> <div style={{ height: '16px' }}></div>
</Dialog> </Dialog>
{/* Image gallery for banner*/} {/* Image gallery for banner*/}
@@ -398,9 +374,9 @@ export class EditProfile extends Component {
title={<DialogTitle title='Choose an banner image' onRequestClose={this.handleCloseBannerGallery} />} title={<DialogTitle title='Choose an banner image' onRequestClose={this.handleCloseBannerGallery} />}
modal={false} modal={false}
open={this.state.openBanner} open={this.state.openBanner}
contentStyle={styles.dialogGallery} contentStyle={this.styles.dialogGallery}
onRequestClose={this.handleCloseBannerGallery} onRequestClose={this.handleCloseBannerGallery}
overlayStyle={{ background: "rgba(0,0,0,0.12)" }} overlayStyle={{ background: 'rgba(0,0,0,0.12)' }}
autoDetectWindowHeight={false} autoDetectWindowHeight={false}
> >
@@ -412,9 +388,9 @@ export class EditProfile extends Component {
title={<DialogTitle title='Choose an avatar image' onRequestClose={this.handleCloseAvatarGallery} />} title={<DialogTitle title='Choose an avatar image' onRequestClose={this.handleCloseAvatarGallery} />}
modal={false} modal={false}
open={this.state.openAvatar} open={this.state.openAvatar}
contentStyle={styles.dialogGallery} contentStyle={this.styles.dialogGallery}
onRequestClose={this.handleCloseAvatarGallery} onRequestClose={this.handleCloseAvatarGallery}
overlayStyle={{ background: "rgba(0,0,0,0.12)" }} overlayStyle={{ background: 'rgba(0,0,0,0.12)' }}
autoDetectWindowHeight={false} autoDetectWindowHeight={false}
> >
@@ -422,23 +398,22 @@ export class EditProfile extends Component {
</Dialog> </Dialog>
</div> </div>
) )
} }
} }
/** /**
* Map dispatch to props * Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers * @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapDispatchToProps = (dispatch, ownProps) => { const mapDispatchToProps = (dispatch: any, ownProps: IEditProfileComponentProps) => {
return { return {
update: (info) => dispatch(userActions.dbUpdateUserInfo(info)), update: (info: Profile) => dispatch(userActions.dbUpdateUserInfo(info)),
onRequestClose: () => dispatch(userActions.closeEditProfile()) onRequestClose: () => dispatch(userActions.closeEditProfile())
} }
} }
/** /**
@@ -447,14 +422,14 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state: any, ownProps: IEditProfileComponentProps) => {
return { return {
open: state.user.openEditProfile, open: state.user.openEditProfile,
info: state.user.info[state.authorize.uid], info: state.user.info[state.authorize.uid],
avatarURL: state.imageGallery.imageURLList avatarURL: state.imageGallery.imageURLList
} }
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(EditProfile) export default connect(mapStateToProps, mapDispatchToProps)(EditProfileComponent as any)

View File

@@ -0,0 +1,58 @@
import { Profile } from 'core/domain/users'
export interface IEditProfileComponentProps {
/**
* User profile
*
* @type {Profile}
* @memberof IEditProfileComponentProps
*/
info?: Profile
/**
* User profile banner addresss
*
* @type {string}
* @memberof IEditProfileComponentProps
*/
banner: string
/**
* User avatar address
*
* @type {string}
* @memberof IEditProfileComponentProps
*/
avatar: string
/**
* User full name
*
* @type {string}
* @memberof IEditProfileComponentProps
*/
fullName: string
/**
* Edit profile dialog is open {true} or not {false}
*
* @type {boolean}
* @memberof IEditProfileComponentProps
*/
open?: boolean
/**
* Update user profile
*
* @memberof IEditProfileComponentProps
*/
update?: (profile: Profile) => void
/**
* On edit profile dialog close event
*
* @memberof IEditProfileComponentProps
*/
onRequestClose?: () => void
}

View File

@@ -0,0 +1,68 @@
export interface IEditProfileComponentState {
/**
* Full name input value
*
* @type {string}
* @memberof IEditProfileComponentState
*/
fullNameInput: string
/**
* Full name input error message
*
* @type {string}
* @memberof IEditProfileComponentState
*/
fullNameInputError: string
/**
* Tag line input value
*
* @type {string}
* @memberof IEditProfileComponentState
*/
tagLineInput: string
/**
* Edit profile page is small size {true} or big {false}
*
* @type {boolean}
* @memberof IEditProfileComponentState
*/
isSmall: boolean
/**
* User's banner URL
*
* @type {string}
* @memberof IEditProfileComponentState
*/
banner: string
/**
* User's avatar URL address
*
* @type {string}
* @memberof IEditProfileComponentState
*/
avatar: string
/**
* Image gallery dialog is open for choosing banner image {true} or not {false}
*
* @type {boolean}
* @memberof IEditProfileComponentState
*/
openBanner: boolean
/**
* Image gallery dialog is open for choosing avatar image {true} or not {false}
*
* @type {boolean}
* @memberof IEditProfileComponentState
*/
openAvatar: boolean
}

View File

@@ -0,0 +1,2 @@
import EditProfileComponent from './EditProfileComponent'
export default EditProfileComponent

View File

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

View File

@@ -0,0 +1,20 @@
import { Profile } from 'core/domain/users/profile'
export interface IFindPeopleComponentProps {
/**
* Load users' profile
*
* @memberof IFindPeopleComponentProps
*/
loadPeople?: () => any
/**
* Users' profile
*
* @type {{[userId: string]: Profile}}
* @memberof IFindPeopleComponentProps
*/
peopleInfo?: {[userId: string]: Profile}
}

View File

@@ -0,0 +1,4 @@
export interface IFindPeopleComponentState {
}

View File

@@ -0,0 +1,2 @@
import FindPeopleComponent from './FindPeopleComponent'
export default FindPeopleComponent

View File

@@ -1,33 +1,35 @@
// - Import react components // - Import react components
import React, {Component} from 'react' import React, { Component } from 'react'
import {connect} from 'react-redux' import { connect } from 'react-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
// - Import app components // - Import app components
import UserBoxList from 'UserBoxList' import UserBoxList from 'components/userBoxList'
import { IFollowersComponentProps } from './IFollowersComponentProps'
import { IFollowersComponentState } from './IFollowersComponentState'
// - Import API // - Import API
// - Import actions // - Import actions
/** /**
* Create component class * Create component class
*/ */
export class Followers extends Component { export class FollowersComponent extends Component<IFollowersComponentProps,IFollowersComponentState> {
static propTypes = { static propTypes = {
} }
/** /**
* Component constructor * Component constructor
* @param {object} props is an object properties of component * @param {object} props is an object properties of component
*/ */
constructor(props){ constructor (props: IFollowersComponentProps) {
super(props) super(props)
//Defaul state // Defaul state
this.state = { this.state = {
} }
@@ -40,24 +42,23 @@ static propTypes = {
* Reneder component DOM * Reneder component DOM
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render() { render () {
return ( return (
<div> <div>
{(this.props.followers && Object.keys(this.props.followers).length !==0) ? (<div> {(this.props.followers && Object.keys(this.props.followers).length !== 0) ? (<div>
<div className='profile__title'> <div className='profile__title'>
Followers Followers
</div> </div>
<UserBoxList users={this.props.followers} /> <UserBoxList users={this.props.followers} />
<div style={{ height: '24px' }}></div> <div style={{ height: '24px' }}></div>
</div>) </div>)
: (<div className='g__title-center'> : (<div className='g__title-center'>
No followers! No followers!
</div>)} </div>)}
</div> </div>
) )
} }
} }
/** /**
* Map dispatch to props * Map dispatch to props
@@ -65,11 +66,11 @@ static propTypes = {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapDispatchToProps = (dispatch,ownProps) => { const mapDispatchToProps = (dispatch: any,ownProps: IFollowersComponentProps) => {
return{ return{
}
} }
}
/** /**
* Map state to props * Map state to props
@@ -77,13 +78,13 @@ static propTypes = {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state,ownProps) => { const mapStateToProps = (state: any,ownProps: IFollowersComponentProps) => {
const { uid } = state.authorize const { uid } = state.authorize
const circles = state.circle ? state.circle.userCircles[uid] : {} const circles = state.circle ? state.circle.userCircles[uid] : {}
return{ return{
followers: circles ? (circles['-Followers'] ? circles['-Followers'].users || {} : {}) : {} followers: circles ? (circles['-Followers'] ? circles['-Followers'].users || {} : {}) : {}
}
} }
}
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps,mapDispatchToProps)(Followers) export default connect(mapStateToProps,mapDispatchToProps)(FollowersComponent as any)

View File

@@ -0,0 +1,12 @@
import { UserFollower } from 'core/domain/circles'
export interface IFollowersComponentProps {
/**
* User followers info
*
* @type {{[userId: string]: UserFollower}}
* @memberof IFindPeopleComponentProps
*/
followers?: {[userId: string]: UserFollower}
}

View File

@@ -0,0 +1,4 @@
export interface IFollowersComponentState {
}

View File

@@ -0,0 +1,2 @@
import FollowersComponent from './FollowersComponent'
export default FollowersComponent

View File

@@ -1,34 +1,35 @@
// - Import react components // - Import react components
import React, {Component} from 'react' import React, { Component } from 'react'
import {connect} from 'react-redux' import { connect } from 'react-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
// - Import app components // - Import app components
import UserBoxList from 'UserBoxList' import UserBoxList from 'components/userBoxList'
// - Import API // - Import API
import CircleAPI from 'CircleAPI' import CircleAPI from 'api/CircleAPI'
import { IFollowingComponentProps } from './IFollowingComponentProps'
import { IFollowingComponentState } from './IFollowingComponentState'
// - Import actions // - Import actions
/** /**
* Create component class * Create component class
*/ */
export class Following extends Component { export class FollowingComponent extends Component<IFollowingComponentProps,IFollowingComponentState> {
static propTypes = { static propTypes = {
} }
/** /**
* Component constructor * Component constructor
* @param {object} props is an object properties of component * @param {object} props is an object properties of component
*/ */
constructor(props){ constructor (props: IFollowingComponentProps) {
super(props) super(props)
//Defaul state // Defaul state
this.state = { this.state = {
} }
@@ -41,25 +42,24 @@ static propTypes = {
* Reneder component DOM * Reneder component DOM
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render() { render () {
return ( return (
<div> <div>
{(this.props.followingUsers && Object.keys(this.props.followingUsers).length !== 0 )? (<div> {(this.props.followingUsers && Object.keys(this.props.followingUsers).length !== 0 ) ? (<div>
<div className='profile__title'> <div className='profile__title'>
Following Following
</div> </div>
<UserBoxList users={this.props.followingUsers} /> <UserBoxList users={this.props.followingUsers} />
<div style={{ height: '24px' }}></div> <div style={{ height: '24px' }}></div>
</div>) : (<div className='g__title-center'> </div>) : (<div className='g__title-center'>
No following user! No following user!
</div>)} </div>)}
</div> </div>
) )
} }
} }
/** /**
* Map dispatch to props * Map dispatch to props
@@ -67,11 +67,11 @@ static propTypes = {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapDispatchToProps = (dispatch,ownProps) => { const mapDispatchToProps = (dispatch: any,ownProp: IFollowingComponentProps) => {
return{ return{
}
} }
}
/** /**
* Map state to props * Map state to props
@@ -79,17 +79,17 @@ static propTypes = {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state,ownProps) => { const mapStateToProps = (state: any,ownProps: IFollowingComponentProps) => {
const { uid } = state.authorize const { uid } = state.authorize
const circles = state.circle ? state.circle.userCircles[uid] : {} const circles = state.circle ? state.circle.userCircles[uid] : {}
const followingUsers = CircleAPI.getFollowingUsers(circles) const followingUsers = CircleAPI.getFollowingUsers(circles)
return { return {
uid, uid,
circles, circles,
followingUsers, followingUsers
}
} }
}
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps,mapDispatchToProps)(Following) export default connect(mapStateToProps,mapDispatchToProps)(FollowingComponent as any)

View File

@@ -0,0 +1,6 @@
import { UserFollower } from 'core/domain/circles'
export interface IFollowingComponentProps {
followingUsers?: {[userId: string]: UserFollower}
}

View File

@@ -0,0 +1,4 @@
export interface IFollowingComponentState {
}

View File

@@ -0,0 +1,2 @@
import FollowingComponent from './FollowingComponent'
export default FollowingComponent

View File

@@ -15,34 +15,36 @@ import SvgAccountCircle from 'material-ui/svg-icons/action/account-circle'
import SvgPeople from 'material-ui/svg-icons/social/people' import SvgPeople from 'material-ui/svg-icons/social/people'
// - Import app components // - Import app components
import Sidebar from 'Sidebar' import Sidebar from 'components/sidebar'
import Blog from 'Blog' import StreamComponent from 'components/stream'
import HomeHeader from 'HomeHeader' import HomeHeader from 'components/homeHeader'
import SidebarContent from 'SidebarContent' import SidebarContent from 'components/sidebarContent'
import SidebarMain from 'SidebarMain' import SidebarMain from 'components/sidebarMain'
import Profile from 'Profile' import Profile from 'components/profile'
import PostPage from 'PostPage' import PostPage from 'components/postPage'
import People from 'People' import People from 'components/people'
// - Import API // - Import API
import CircleAPI from 'CircleAPI' import CircleAPI from 'api/CircleAPI'
// - Import actions // - Import actions
import * as globalActions from 'globalActions' import * as globalActions from 'actions/globalActions'
import { IHomeComponentProps } from './IHomeComponentProps'
import { IHomeComponentState } from './IHomeComponentState'
// - Create Home component class // - Create Home component class
export class Home extends Component { export class HomeComponent extends Component<IHomeComponentProps, IHomeComponentState> {
// Constructor // Constructor
constructor(props) { constructor (props: IHomeComponentProps) {
super(props) super(props)
// Default state // Default state
this.state = { this.state = {
sidebarOpen: () => _, sidebarOpen: () => _,
sidebarStatus: true, sidebarStatus: true,
sidebaOverlay: false sidebarOverlay: false
} }
// Binding function to `this` // Binding function to `this`
@@ -57,37 +59,35 @@ export class Home extends Component {
* handle close sidebar * handle close sidebar
*/ */
handleCloseSidebar = () => { handleCloseSidebar = () => {
this.state.sidebarOpen(false, 'overlay') this.state.sidebarOpen!(false, 'overlay')
} }
/** /**
* Change sidebar overlay status * Change sidebar overlay status
* @param {boolean} status if is true, the sidebar is on overlay status * @param {boolean} status if is true, the sidebar is on overlay status
*/ */
sidebarOverlay = (status) => { sidebarOverlay = (status: boolean) => {
this.setState({ this.setState({
sidebarOverlay: status sidebarOverlay: status
}) })
} }
/** /**
* Pass function to change sidebar status * Pass the function to change sidebar status
* @param {boolean} open is a function callback to change sidebar status out of sidebar component * @param {boolean} open is a function callback to change sidebar status out of sidebar component
*/ */
sidebar = (open) => { sidebar = (open: (status: boolean, source: string) => void) => {
this.setState({ this.setState({
sidebarOpen: open sidebarOpen: open
}) })
} }
/** /**
* Change sidebar status if is open or not * Change sidebar status if is open or not
* @param {boolean} status is true, if the sidebar is open * @param {boolean} status is true, if the sidebar is open
*/ */
sidebarStatus = (status) => { sidebarStatus = (status: boolean) => {
this.setState({ this.setState({
sidebarStatus: status sidebarStatus: status
}) })
@@ -95,60 +95,59 @@ export class Home extends Component {
/** /**
* Render DOM component * Render DOM component
* *
* @returns DOM * @returns DOM
* *
* @memberof Home * @memberof Home
*/ */
render() { render () {
return ( return (
<div id="home"> <div id='home'>
<HomeHeader sidebar={this.state.sidebarOpen} sidebarStatus={this.state.sidebarStatus} /> <HomeHeader sidebar={this.state.sidebarOpen} sidebarStatus={this.state.sidebarStatus} />
<Sidebar overlay={this.sidebarOverlay} open={this.sidebar} status={this.sidebarStatus}> <Sidebar overlay={this.sidebarOverlay} open={this.sidebar} status={this.sidebarStatus}>
<SidebarContent> <SidebarContent>
<Menu style={{ color: "rgb(117, 117, 117)", width: '210px' }}> <Menu style={{ color: 'rgb(117, 117, 117)', width: '210px' }}>
{this.state.sidebarOverlay {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> ? <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='/'><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={`/${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> <NavLink to='/people'><MenuItem primaryText='People' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgPeople />} /></NavLink>
<Divider /> <Divider />
<NavLink to='/settings'><MenuItem primaryText="Settings" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgSettings />} /></NavLink> <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> <NavLink to='#'><MenuItem primaryText='Send feedback' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgFeedback />} /></NavLink>
</Menu> </Menu>
</SidebarContent> </SidebarContent>
<SidebarMain> <SidebarMain>
<Switch> <Switch>
<Route path="/people/:tab?" render={() => { <Route path='/people/:tab?' render={() => {
return ( return (
this.props.authed this.props.authed
? <People /> ? <People />
: <Redirect to="/login" /> : <Redirect to='/login' />
) )
}} /> }} />
<Route path="/tag/:tag" render={({match}) => { <Route path='/tag/:tag' render={({match}) => {
return ( return (
this.props.authed this.props.authed
? <div className="blog"><Blog displayWriting={false} homeTitle={`#${match.params.tag}`} posts={this.props.mergedPosts} /></div> ? <div className='blog'><StreamComponent displayWriting={false} homeTitle={`#${match.params.tag}`} posts={this.props.mergedPosts} /></div>
: <Redirect to="/login" /> : <Redirect to='/login' />
) )
}} /> }} />
<Route path="/:userId/posts/:postId/:tag?" component={PostPage} /> <Route path='/:userId/posts/:postId/:tag?' component={PostPage} />
<Route path="/:userId" component={Profile} /> <Route path='/:userId' component={Profile} />
<Route path="/" render={() => { <Route path='/' render={() => {
return ( return (
this.props.authed this.props.authed
? <div className="blog"><Blog homeTitle='Home' posts={this.props.mergedPosts} displayWriting={true} /></div> ? <div className='blog'><StreamComponent homeTitle='Home' posts={this.props.mergedPosts} displayWriting={true} /></div>
: <Redirect to="/login" /> : <Redirect to='/login' />
) )
}} /> }} />
</Switch> </Switch>
@@ -167,10 +166,9 @@ export class Home extends Component {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapDispatchToProps = (dispatch, ownProps) => { const mapDispatchToProps = (dispatch: any, ownProps: IHomeComponentProps) => {
return { return {
} }
} }
@@ -180,15 +178,15 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state: any, ownProps: IHomeComponentProps) => {
const { uid } = state.authorize const { uid } = state.authorize
let mergedPosts = {} let mergedPosts = {}
const circles = state.circle ? (state.circle.userCircles[uid] || {}) : {} const circles = state.circle ? (state.circle.userCircles[uid] || {}) : {}
const followingUsers = CircleAPI.getFollowingUsers(circles) const followingUsers = CircleAPI.getFollowingUsers(circles)
const posts = state.post.userPosts ? state.post.userPosts[state.authorize.uid] : {} const posts = state.post.userPosts ? state.post.userPosts[state.authorize.uid] : {}
Object.keys(followingUsers).forEach((userId)=>{ Object.keys(followingUsers).forEach((userId) => {
let newPosts = state.post.userPosts ? state.post.userPosts[userId] : {} let newPosts = state.post.userPosts ? state.post.userPosts[userId] : {}
_.merge(mergedPosts,newPosts) _.merge(mergedPosts,newPosts)
}) })
_.merge(mergedPosts,posts) _.merge(mergedPosts,posts)
return { return {
@@ -199,4 +197,4 @@ const mapStateToProps = (state, ownProps) => {
} }
// - Connect component to redux store // - Connect component to redux store
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Home)) export default withRouter(connect(mapStateToProps, mapDispatchToProps)(HomeComponent as any))

View File

@@ -0,0 +1,28 @@
import { Post } from 'core/domain/posts'
export interface IHomeComponentProps {
/**
* Current user is authenticated {true} or not {false}
*
* @type {boolean}
* @memberof IHomeComponentProps
*/
authed?: boolean
/**
* User identifier
*
* @type {string}
* @memberof IHomeComponentProps
*/
uid: string
/**
* Merged all users posts to show in stream
*
* @type {{[postId: string]: Post}}
* @memberof IHomeComponentProps
*/
mergedPosts?: {[postId: string]: Post}
}

View File

@@ -0,0 +1,27 @@
export interface IHomeComponentState {
/**
* Change sidebar status to {open(status:true)/close(status:false)}
*
* @type {(status: boolean, state: string)}
* @memberof IHomeComponentState
*/
sidebarOpen: (status: boolean, source: string) => void
/**
* Sidebar status
*
* @type {boolean}
* @memberof IHomeComponentState
*/
sidebarStatus: boolean
/**
* Sidebar overlay status
*
* @type {boolean}
* @memberof IHomeComponentState
*/
sidebarOverlay: boolean
}

View File

@@ -0,0 +1,2 @@
import HomeComponent from './HomeComponent'
export default HomeComponent

View File

@@ -14,24 +14,43 @@ import Paper from 'material-ui/Paper'
import NotificationsIcon from 'material-ui/svg-icons/social/notifications' import NotificationsIcon from 'material-ui/svg-icons/social/notifications'
import EventListener, { withOptions } from 'react-event-listener' import EventListener, { withOptions } from 'react-event-listener'
// - Import components // - Import components
import UserAvatar from 'UserAvatar' import UserAvatarComponent from 'components/userAvatar'
import Notify from 'Notify' import Notify from 'components/notify'
// - Import actions // - Import actions
import * as globalActions from 'globalActions' import * as globalActions from 'actions/globalActions'
import * as authorizeActions from 'authorizeActions' import { authorizeActions } from 'actions'
import { IHomeHeaderComponentProps } from './IHomeHeaderComponentProps'
import { IHomeHeaderComponentState } from './IHomeHeaderComponentState'
// - Create HomeHeader component class // - Create HomeHeader component class
export class HomeHeader extends Component { export class HomeHeaderComponent extends Component<IHomeHeaderComponentProps,IHomeHeaderComponentState> {
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'
}
}
/** /**
* Component constructor * Component constructor
* @param {object} props is an object properties of component * @param {object} props is an object properties of component
*/ */
constructor(props) { constructor (props: IHomeHeaderComponentProps) {
super(props) super(props)
// Default state // Default state
@@ -56,12 +75,10 @@ export class HomeHeader extends Component {
} }
/** /**
* Handle close notification menu * Handle close notification menu
* *
* *
* @memberof HomeHeader * @memberof HomeHeader
*/ */
handleCloseNotify = () => { handleCloseNotify = () => {
@@ -70,78 +87,80 @@ export class HomeHeader extends Component {
}) })
} }
// On click toggle sidebar // On click toggle sidebar
onToggleSidebar = () => { onToggleSidebar = () => {
if (this.props.sidebarStatus) { if (this.props.sidebarStatus) {
this.props.sidebar(false) this.props.sidebar!(false,'onToggle')
} else { } else {
this.props.sidebar(true) this.props.sidebar!(true,'onToggle')
} }
} }
/** /**
* Handle notification touch * Handle notification touch
* *
* *
* @memberof HomeHeader * @memberof HomeHeader
*/ */
handleNotifyTouchTap = (event) => { handleNotifyTouchTap = (event: any) => {
// This prevents ghost click. // This prevents ghost click.
event.preventDefault() event.preventDefault()
this.setState({ this.setState({
openNotifyMenu: true, openNotifyMenu: true,
anchorEl: event.currentTarget, anchorEl: event.currentTarget
}) })
} }
/** /**
* Handle touch on user avatar for popover * Handle touch on user avatar for popover
* *
* *
* @memberof HomeHeader * @memberof HomeHeader
*/ */
handleAvatarTouchTap = (event) => { handleAvatarTouchTap = (event: any) => {
// This prevents ghost click. // This prevents ghost click.
event.preventDefault() event.preventDefault()
this.setState({ this.setState({
openAvatarMenu: true, openAvatarMenu: true,
anchorEl: event.currentTarget, anchorEl: event.currentTarget
}) })
} }
/** /**
* Handle logout user * Handle logout user
* *
* *
* @memberof HomeHeader * @memberof HomeHeader
*/ */
handleLogout = () => { handleLogout = () => {
this.props.logout() this.props.logout!()
} }
/** /**
* Handle close popover * Handle close popover
* *
* *
* @memberof HomeHeader * @memberof HomeHeader
*/ */
handleRequestClose = () => { handleRequestClose = () => {
this.setState({ this.setState({
openAvatarMenu: false, openAvatarMenu: false
}) })
} }
handleKeyUp = () => {
// TODO: Handle key up on press ESC to close menu
}
/** /**
* Handle resize event for window to manipulate home header status * Handle resize event for window to manipulate home header status
* @param {event} evt is the event is passed by winodw resize event * @param {event} evt is the event is passed by winodw resize event
*/ */
handleResize = (evt) => { handleResize = (event: any) => {
// Set initial state // Set initial state
let width = window.innerWidth let width = window.innerWidth
@@ -151,8 +170,7 @@ export class HomeHeader extends Component {
showTitle: true showTitle: true
}) })
} } else if (width < 600 && this.state.showTitle) {
else if (width < 600 && this.state.showTitle) {
this.setState({ this.setState({
showTitle: false showTitle: false
@@ -160,54 +178,30 @@ export class HomeHeader extends Component {
} }
} }
componentDidMount = () => { componentDidMount () {
this.handleResize() this.handleResize(null)
} }
// Render app DOM component // Render app DOM component
render() { render () {
/**
* Styles
*/
let 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 ( return (
<Toolbar style={styles.toolbarStyle} className="g__greenBox"> <Toolbar style={this.styles.toolbarStyle as any} className='g__greenBox'>
<EventListener <EventListener
target="window" target='window'
onResize={this.handleResize} onResize={this.handleResize}
onKeyUp={this.handleKeyUp} onKeyUp={this.handleKeyUp}
/> />
{/* Left side */} {/* Left side */}
<ToolbarGroup firstChild={true}> <ToolbarGroup firstChild={true}>
<IconButton iconStyle={{ color: "#fff" }} onClick={this.onToggleSidebar} > <IconButton iconStyle={{ color: '#fff' }} onClick={this.onToggleSidebar} >
<SvgDehaze style={{ color: "#fff", marginLeft: "15px", cursor: "pointer" }} /> <SvgDehaze style={{ color: '#fff', marginLeft: '15px', cursor: 'pointer' }} />
</IconButton> </IconButton>
{/* Header title */} {/* Header title */}
<ToolbarTitle style={{ color: "#fff", marginLeft: "15px" }} text="Green" /> <ToolbarTitle style={{ color: '#fff', marginLeft: '15px' }} text='Green' />
{this.state.showTitle ? <div className="homeHeader__page">{this.props.title}</div> : ''} {this.state.showTitle ? <div className='homeHeader__page'>{this.props.title}</div> : ''}
</ToolbarGroup> </ToolbarGroup>
<ToolbarGroup> <ToolbarGroup>
@@ -215,26 +209,25 @@ export class HomeHeader extends Component {
{/* Notification */} {/* Notification */}
<ToolbarGroup lastChild={true}> <ToolbarGroup lastChild={true}>
<div className="homeHeader__right"> <div className='homeHeader__right'>
{this.props.notifyCount > 0 ? (<IconButton tooltip="Notifications" onTouchTap={this.handleNotifyTouchTap}> {this.props.notifyCount! > 0 ? (<IconButton tooltip='Notifications' onTouchTap={this.handleNotifyTouchTap}>
<div className="homeHeader__notify"> <div className='homeHeader__notify'>
<div className='title'>{this.props.notifyCount}</div> <div className='title'>{this.props.notifyCount}</div>
</div> </div>
</IconButton>) </IconButton>)
: (<IconButton tooltip="Notifications" onTouchTap={this.handleNotifyTouchTap}> : (<IconButton tooltip='Notifications' onTouchTap={this.handleNotifyTouchTap}>
<NotificationsIcon color='rgba(255, 255, 255, 0.87)' /> <NotificationsIcon color='rgba(255, 255, 255, 0.87)' />
</IconButton>)} </IconButton>)}
<Notify open={this.state.openNotifyMenu} anchorEl={this.state.anchorEl} onRequestClose={this.handleCloseNotify}/> <Notify open={this.state.openNotifyMenu} anchorEl={this.state.anchorEl} onRequestClose={this.handleCloseNotify}/>
{/* User avatar*/} {/* User avatar*/}
<UserAvatar <UserAvatarComponent
onTouchTap={this.handleAvatarTouchTap} onTouchTap={this.handleAvatarTouchTap}
fullName={this.props.fullName} fullName={this.props.fullName!}
fileName={this.props.avatar} fileName={this.props.avatar!}
size={32} size={32}
style={styles.avatarStyle} style={this.styles.avatarStyle}
/> />
<Popover <Popover
open={this.state.openAvatarMenu} open={this.state.openAvatarMenu}
@@ -244,8 +237,8 @@ export class HomeHeader extends Component {
onRequestClose={this.handleRequestClose} onRequestClose={this.handleRequestClose}
> >
<Menu> <Menu>
<MenuItem style={{ backgroundColor: 'white', color: blue500, fontSize: '14px' }} primaryText="MY ACCOUNT" /> <MenuItem style={{ backgroundColor: 'white', color: blue500, fontSize: '14px' }} primaryText='MY ACCOUNT' />
<MenuItem primaryText="LOGOUT" style={{ fontSize: '14px' }} onClick={this.handleLogout.bind(this)} /> <MenuItem primaryText='LOGOUT' style={{ fontSize: '14px' }} onClick={this.handleLogout.bind(this)} />
</Menu> </Menu>
</Popover> </Popover>
@@ -254,26 +247,24 @@ export class HomeHeader extends Component {
</Toolbar> </Toolbar>
) )
} }
} }
// - Map dispatch to props // - Map dispatch to props
const mapDispatchToProps = (dispatch, ownProps) => { const mapDispatchToProps = (dispatch: Function, ownProps: IHomeHeaderComponentProps) => {
return { return {
logout: () => dispatch(authorizeActions.dbLogout()) logout: () => dispatch(authorizeActions.dbLogout())
} }
} }
// - Map state to props // - Map state to props
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state: any, ownProps: IHomeHeaderComponentProps) => {
let notifyCount = state.notify.userNotifies let notifyCount = state.notify.userNotifies
? Object ? Object
.keys(state.notify.userNotifies) .keys(state.notify.userNotifies)
.filter((key)=> !state.notify.userNotifies[key].isSeen).length .filter((key) => !state.notify.userNotifies[key].isSeen).length
: 0 : 0
return { return {
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : '', avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : '',
@@ -284,4 +275,4 @@ const mapStateToProps = (state, ownProps) => {
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(HomeHeader) export default connect(mapStateToProps, mapDispatchToProps)(HomeHeaderComponent as any)

View File

@@ -0,0 +1,63 @@
import StringAPI from 'api/StringAPI'
export interface IHomeHeaderComponentProps {
/**
* Sidebar is open {true} or not {false}
*
* @type {boolean}
* @memberof IHomeHeaderComponentProps
*/
sidebarStatus?: boolean
/**
* Logout user
*
* @memberof IHomeHeaderComponentProps
*/
logout?: () => void
/**
* Handle on resize window event
*
* @memberof IHomeHeaderComponentProps
*/
handleResize?: (event: any) => void
/**
* Number of notifications
*
* @memberof IHomeHeaderComponentProps
*/
notifyCount?: number
/**
* User full name
*
* @type {string}
* @memberof IHomeHeaderComponentProps
*/
fullName?: string
/**
* User's avatar URL address
*
* @type {string}
* @memberof IHomeHeaderComponentProps
*/
avatar?: string
/**
* Top bar title
*
* @type {string}
* @memberof IHomeHeaderComponentProps
*/
title?: string
/**
* Toggle sidebar
*
* @memberof IHomeHeaderComponentProps
*/
sidebar?: (status: boolean, source: string) => void
}

View File

@@ -0,0 +1,35 @@
export interface IHomeHeaderComponentState {
/**
* Popover menu on avatar is open {true} or not {false}
*
* @type {boolean}
* @memberof IHomeHeaderComponentState
*/
openAvatarMenu: boolean
/**
* Show top bar title {true} or not {false}
*
* @type {boolean}
* @memberof IHomeHeaderComponentState
*/
showTitle: boolean
/**
* Notification menu is open {true} or not {false}
*
* @type {boolean}
* @memberof IHomeHeaderComponentState
*/
openNotifyMenu: boolean
/**
* This is the DOM element that will be used to set the position of the popover.
*
* @type {*}
* @memberof IHomeHeaderComponentState
*/
anchorEl?: HTMLElement
}

View File

@@ -0,0 +1,2 @@
import HomeHeaderComponent from './HomeHeaderComponent'
export default HomeHeaderComponent

View File

@@ -0,0 +1,48 @@
import { Image } from 'core/domain/imageGallery'
export interface IImageGalleryComponentProps {
/**
* Select image from image gallery
*
* @type {(URL: string,fullPath: string)}
* @memberof IImageGalleryComponentProps
*/
set?: (URL: string,fullPath: string) => void
/**
* Delete an image
*
* @memberof IImageGalleryComponentProps
*/
deleteImage?: (imageId: string) => void
/**
* Save image in image gallery
*
* @memberof IImageGalleryComponentProps
*/
saveImageGallery?: (URL: string,fullPath: string) => void
/**
* Change progress state
*
* @memberof IImageGalleryComponentProps
*/
progressChange?: (percentage: number, status: boolean) => void
/**
* Close image gallery
*
* @memberof IImageGalleryComponentProps
*/
close?: () => void
/**
* List of image in image gallery
*
* @type {Image[]}
* @memberof IImageGalleryComponentProps
*/
images?: Image[]
}

View File

@@ -0,0 +1,4 @@
export interface IImageGalleryComponentState {
}

View File

@@ -0,0 +1,256 @@
// - 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 'actions/imageGalleryActions'
import * as globalActions from 'actions/globalActions'
// - Import app components
import Img from 'components/img'
// - Import API
import FileAPI from 'api/FileAPI'
import { IImageGalleryComponentProps } from './IImageGalleryComponentProps'
import { IImageGalleryComponentState } from './IImageGalleryComponentState'
import { Image } from 'core/domain/imageGallery'
/**
* Create ImageGallery component class
*/
export class ImageGalleryComponent extends Component<IImageGalleryComponentProps, IImageGalleryComponentState> {
static propTypes = {
/**
* Callback function to ser image url on parent component
*/
open: PropTypes.func
}
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
},
deleteImage: {
marginLeft: '5px',
cursor: 'pointer'
},
addImage: {
marginRight: '5px',
cursor: 'pointer'
}
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor (props: IImageGalleryComponentProps) {
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 = (event: any, URL: string,fullPath: string) => {
this.props.set!(URL,fullPath)
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 = (event: any, id: string) => {
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: any) => {
const { resizedImage, fileName } = event.detail
const {saveImageGallery, progressChange} = this.props
FileAPI.uploadImage(resizedImage, fileName, (percent: number, status: boolean) => {
progressChange!(percent,status)
}).then((result) => {
/* Add image to image gallery */
saveImageGallery!(result.downloadURL,result.metadata.fullPath)
})
}
/**
* Handle on change file upload
*/
onFileChange = (event: any) => {
const extension = FileAPI.getExtension(event.target.files[0].name)
let fileName = (`${uuid()}.${extension}`)
let image = FileAPI.constraintImage(event.target.files[0], fileName)
}
/**
* Hide image gallery
*/
close = () => {
this.props.close!()
}
imageList = () => {
return this.props.images!.map((image: Image, index) => {
return (<GridTile
key={image.id!}
title={<SvgDelete hoverColor={grey200} color='white' style={this.styles.deleteImage as any} onClick={evt => this.handleDeleteImage(evt, image.id!)} />}
subtitle={<span></span>}
actionIcon={<SvgAddImage hoverColor={grey200} color='white' style={this.styles.addImage as any} onClick={evt => this.handleSetImage(evt, image.URL,image.fullPath)} />}
>
<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.URL} 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}
*/
return (
<div style={this.styles.root as any}>
<GridList
cellHeight={180}
style={this.styles.gridList as any}
>
<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={this.styles.uploadButton}
containerElement='label'
>
<input type='file' onChange={this.onFileChange} accept='image/*' style={this.styles.uploadInput as any} />
</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: any, ownProps: IImageGalleryComponentProps) => {
return {
saveImageGallery: (imageURL: string,imageFullPath: string) => dispatch(imageGalleryActions.dbSaveImage(imageURL,imageFullPath)),
deleteImage: (id: string) => dispatch(imageGalleryActions.dbDeleteImage(id)),
progressChange : (percent: number,status: boolean) => dispatch(globalActions.progressChange(percent, status))
}
}
/**
* 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: any) => {
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)(ImageGalleryComponent as any)

View File

@@ -0,0 +1,2 @@
import ImageGalleryComponent from './ImageGalleryComponent'
export default ImageGalleryComponent

View File

@@ -0,0 +1,19 @@
export interface IImgComponentProps {
/**
* Image file name
*
* @type {string}
* @memberof IImgComponentProps
*/
fileName: string
/**
* Image style sheet
*
* @type {{}}
* @memberof IImgComponentProps
*/
style?: {}
}

View File

@@ -0,0 +1,11 @@
export interface IImgComponentState {
/**
* Image is loaded {true} or not {false}
*
* @type {boolean}
* @memberof IImgComponentProps
*/
isImageLoaded?: boolean
}

View File

@@ -4,40 +4,51 @@ import PropTypes from 'prop-types'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import SvgImage from 'material-ui/svg-icons/image/image' import SvgImage from 'material-ui/svg-icons/image/image'
// - Import app components // - Import app components
// - Import API // - Import API
// - Import actions // - Import actions
import * as imageGalleryActions from 'imageGalleryActions' import * as imageGalleryActions from 'actions/imageGalleryActions'
import { IImgComponentProps } from './IImgComponentProps'
import { IImgComponentState } from './IImgComponentState'
/** /**
* Create component class * Create component class
*/ */
export class Img extends Component { export class ImgComponent extends Component<IImgComponentProps,IImgComponentState> {
static propTypes = { styles = {
loding: {
/** display: 'flex',
* Use for getting url address from server justifyContent: 'center',
*/ alignItems: 'center',
fileName: PropTypes.string, width: '100%',
/** height: '100px',
* Avatar style position: 'relative',
*/ color: '#cacecd',
style: PropTypes.object fontWeight: 100
},
loadingContent: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
},
loadingImage: {
fill: 'aliceblue',
width: '50px',
height: '50px'
}
} }
/** /**
* Component constructor * Component constructor
* @param {object} props is an object properties of component * @param {object} props is an object properties of component
*/ */
constructor(props) { constructor (props: IImgComponentProps) {
super(props) super(props)
//Defaul state // Defaul state
this.state = { this.state = {
isImageLoaded: false isImageLoaded: false
} }
@@ -49,7 +60,7 @@ export class Img extends Component {
/** /**
* Will be called on loading image * Will be called on loading image
* *
* @memberof Img * @memberof Img
*/ */
handleLoadImage = () => { handleLoadImage = () => {
@@ -62,38 +73,16 @@ export class Img extends Component {
* Reneder component DOM * Reneder component DOM
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render() { 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 { fileName, style } = this.props
let { isImageLoaded } = this.state let { isImageLoaded } = this.state
return ( return (
<div> <div>
<img onLoad={this.handleLoadImage} src={fileName || ''} style={isImageLoaded ? style : { display: 'none' }} /> <img onLoad={this.handleLoadImage} src={fileName || ''} style={isImageLoaded ? style : { display: 'none' }} />
<div style={{ backgroundColor: 'blue' }} style={isImageLoaded ? { display: 'none' } : styles.loding}> <div style={Object.assign({},{ backgroundColor: 'white' }, isImageLoaded ? { display: 'none' } : this.styles.loding) }>
<div style={styles.loadingContent}> <div style={this.styles.loadingContent as any}>
<SvgImage style={styles.loadingImage} /> <SvgImage style={this.styles.loadingImage} />
<div>Image has not loaded</div> <div>Image has not loaded</div>
</div> </div>
</div> </div>
@@ -102,17 +91,15 @@ export class Img extends Component {
} }
} }
/** /**
* Map dispatch to props * Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers * @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapDispatchToProps = (dispatch, ownProps) => { const mapDispatchToProps = (dispatch: any, ownProps: IImgComponentProps) => {
return { return {
} }
} }
@@ -122,7 +109,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state: any, ownProps: IImgComponentProps) => {
return { return {
avatarURL: state.imageGallery.imageURLList, avatarURL: state.imageGallery.imageURLList,
imageRequests: state.imageGallery.imageRequests imageRequests: state.imageGallery.imageRequests
@@ -130,4 +117,4 @@ const mapStateToProps = (state, ownProps) => {
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(Img) export default connect(mapStateToProps, mapDispatchToProps)(ImgComponent as any)

View File

@@ -0,0 +1,2 @@
import ImgComponent from './ImgComponent'
export default ImgComponent

View File

@@ -0,0 +1,42 @@
export interface IImgCoverComponentProps {
/**
* Image file name
*
* @type {string}
* @memberof IImgCoverComponentProps
*/
fileName: string
/**
* Image style sheet
*
* @type {{}}
* @memberof IImgCoverComponentProps
*/
style?: {}
/**
* Image with
*
* @type {string}
* @memberof IImgCoverComponentProps
*/
width?: string
/**
* Image height
*
* @type {string}
* @memberof IImgCoverComponentProps
*/
height?: string
/**
* Image border radius
*
* @type {string}
* @memberof IImgCoverComponentProps
*/
borderRadius?: string
}

View File

@@ -0,0 +1,12 @@
export interface IImgCoverComponentState {
/**
* Image is loaded {true} or not {false}
*
* @type {boolean}
* @memberof IImgCoverComponentProps
*/
isImageLoaded: boolean
}

View File

@@ -6,17 +6,17 @@ import SvgImage from 'material-ui/svg-icons/image/image'
// - Import app components // - Import app components
// - Import API // - Import API
// - Import actions // - Import actions
import * as imageGalleryActions from 'imageGalleryActions' import * as imageGalleryActions from 'actions/imageGalleryActions'
import { IImgCoverComponentProps } from './IImgCoverComponentProps'
import { IImgCoverComponentState } from './IImgCoverComponentState'
/** /**
* Create component class * Create component class
*/ */
export class ImgCover extends Component { export class ImgCoverComponent extends Component<IImgCoverComponentProps,IImgCoverComponentState> {
static propTypes = { static propTypes = {
@@ -47,14 +47,42 @@ export class ImgCover extends Component {
]) ])
} }
styles = {
cover: {
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center'
},
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'
}
}
/** /**
* Component constructor * Component constructor
* @param {object} props is an object properties of component * @param {object} props is an object properties of component
*/ */
constructor(props) { constructor (props: IImgCoverComponentProps) {
super(props) super(props)
//Defaul state // Defaul state
this.state = { this.state = {
isImageLoaded: false isImageLoaded: false
} }
@@ -65,8 +93,8 @@ export class ImgCover extends Component {
/** /**
* Will be called on loading image * Will be called on loading image
* *
* @memberof Img * @memberof ImgCoverComponent
*/ */
handleLoadImage = () => { handleLoadImage = () => {
this.setState({ this.setState({
@@ -78,54 +106,24 @@ export class ImgCover extends Component {
* Reneder component DOM * Reneder component DOM
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render() { render () {
let { fileName, style } = this.props let { fileName, style } = this.props
let { isImageLoaded } = this.state let { isImageLoaded } = this.state
/**
* Styles
*/
const styles = {
cover: {
backgroundImage: 'url(' + (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 ( return (
<div> <div>
<div style={Object.assign({},styles.cover,style)}> <div style={Object.assign({},this.styles.cover,{
backgroundImage: 'url(' + (fileName || '') + ')',
width: this.props.width,
height: this.props.height,
borderRadius: this.props.borderRadius
},style)}>
{this.props.children} {this.props.children}
</div> </div>
<div style={{ backgroundColor: 'blue' }} style={isImageLoaded ? { display: 'none' } : styles.loding}> <div style={Object.assign({},{ backgroundColor: 'blue' },isImageLoaded ? { display: 'none' } : this.styles.loding)}>
<div style={styles.loadingContent}> <div style={this.styles.loadingContent as any}>
<SvgImage style={styles.loadingImage} /> <SvgImage style={this.styles.loadingImage} />
<div>Image has not loaded</div> <div>Image has not loaded</div>
</div> </div>
</div> </div>
@@ -135,15 +133,13 @@ export class ImgCover extends Component {
} }
} }
/** /**
* Map dispatch to props * Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers * @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapDispatchToProps = (dispatch, ownProps) => { const mapDispatchToProps = (dispatch: any, ownProps: IImgCoverComponentProps) => {
return { return {
} }
} }
@@ -154,7 +150,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state: any, ownProps: IImgCoverComponentProps) => {
return { return {
avatarURL: state.imageGallery.imageURLList, avatarURL: state.imageGallery.imageURLList,
imageRequests: state.imageGallery.imageRequests imageRequests: state.imageGallery.imageRequests
@@ -163,4 +159,4 @@ const mapStateToProps = (state, ownProps) => {
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(ImgCover) export default connect(mapStateToProps, mapDispatchToProps)(ImgCoverComponent as any)

View File

@@ -0,0 +1,2 @@
import ImgCoverComponent from './ImgCoverComponent'
export default ImgCoverComponent

View File

@@ -0,0 +1,16 @@
export interface ILoginComponentProps {
/**
* Login a user
*
* @memberof ILoginComponentProps
*/
login?: (email: string , password: string) => any
/**
* Redirect to signup page
*
* @memberof ILoginComponentProps
*/
signupPage?: () => any
}

View File

@@ -0,0 +1,43 @@
export interface ILoginComponentState {
/**
* Email input value
*
* @type {string}
* @memberof ILoginComponentState
*/
emailInput: string
/**
* Email input error text
*
* @type {string}
* @memberof ILoginComponentState
*/
emailInputError: string
/**
* Password input value
*
* @type {string}
* @memberof ILoginComponentState
*/
passwordInput: string
/**
* Password input error text
*
* @type {string}
* @memberof ILoginComponentState
*/
passwordInputError: string
/**
* Confirm input error text
*
* @type {string}
* @memberof ILoginComponentState
*/
confirmInputError: string
}

View File

@@ -9,17 +9,18 @@ import RaisedButton from 'material-ui/RaisedButton'
import FlatButton from 'material-ui/FlatButton' import FlatButton from 'material-ui/FlatButton'
// - Import actions // - Import actions
import * as authorizeActions from 'authorizeActions' import * as authorizeActions from 'actions/authorizeActions'
import { ILoginComponentProps } from './ILoginComponentProps'
import { ILoginComponentState } from './ILoginComponentState'
// - Create Login component class // - Create Login component class
export class Login extends Component { export class LoginComponent extends Component<ILoginComponentProps,ILoginComponentState> {
/** /**
* Component constructor * Component constructor
* @param {object} props is an object properties of component * @param {object} props is an object properties of component
*/ */
constructor(props) { constructor (props: ILoginComponentProps) {
super(props) super(props)
this.state = { this.state = {
@@ -27,8 +28,7 @@ export class Login extends Component {
emailInputError: '', emailInputError: '',
passwordInput: '', passwordInput: '',
passwordInputError: '', passwordInputError: '',
confirmInputError: ''
} }
// Binding function to `this` // Binding function to `this`
this.handleForm = this.handleForm.bind(this) this.handleForm = this.handleForm.bind(this)
@@ -39,15 +39,14 @@ export class Login extends Component {
* Handle data on input change * Handle data on input change
* @param {event} evt is an event of inputs of element on change * @param {event} evt is an event of inputs of element on change
*/ */
handleInputChange = (evt) => { handleInputChange = (event: any) => {
const target = evt.target const target = event.target
const value = target.type === 'checkbox' ? target.checked : target.value const value = target.type === 'checkbox' ? target.checked : target.value
const name = target.name const name = target.name
this.setState({ this.setState({
[name]: value [name]: value
}) })
switch (name) { switch (name) {
case 'emailInput': case 'emailInput':
this.setState({ this.setState({
@@ -88,7 +87,7 @@ export class Login extends Component {
} }
if (!error) { if (!error) {
this.props.login( this.props.login!(
this.state.emailInput, this.state.emailInput,
this.state.passwordInput this.state.passwordInput
) )
@@ -96,48 +95,46 @@ export class Login extends Component {
} }
/** /**
* Reneder component DOM * Reneder component DOM
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render() { render () {
const paperStyle = { const paperStyle = {
minHeight: 370, minHeight: 370,
width: 450, width: 450,
margin: 20,
textAlign: 'center', textAlign: 'center',
display: 'block', display: 'block',
margin: "auto" margin: 'auto'
} }
return ( return (
<form> <form>
<h1 style={{ <h1 style={{
textAlign: "center", textAlign: 'center',
padding: "20px", padding: '20px',
fontSize: "30px", fontSize: '30px',
fontWeight: 500, fontWeight: 500,
lineHeight: "32px", lineHeight: '32px',
margin: "auto", margin: 'auto',
color: "rgba(138, 148, 138, 0.2)" color: 'rgba(138, 148, 138, 0.2)'
}}>Green</h1> }}>Green</h1>
<div className="animate-bottom"> <div className='animate-bottom'>
<Paper style={paperStyle} zDepth={1} rounded={false} > <Paper style={paperStyle} zDepth={1} rounded={false} >
<div style={{ padding: "48px 40px 36px" }}> <div style={{ padding: '48px 40px 36px' }}>
<div style={{ <div style={{
paddingLeft: "40px", paddingLeft: '40px',
paddingRight: "40px" paddingRight: '40px'
}}> }}>
<h2 style={{ <h2 style={{
textAlign: "left", textAlign: 'left',
paddingTop: "16px", paddingTop: '16px',
fontSize: "24px", fontSize: '24px',
fontWeight: 400, fontWeight: 400,
lineHeight: "32px", lineHeight: '32px',
margin: 0 margin: 0
}}>Sign in</h2> }}>Sign in</h2>
</div> </div>
@@ -145,29 +142,29 @@ export class Login extends Component {
<TextField <TextField
onChange={this.handleInputChange} onChange={this.handleInputChange}
errorText={this.state.emailInputError} errorText={this.state.emailInputError}
name="emailInput" name='emailInput'
floatingLabelStyle={{ fontSize: "15px" }} floatingLabelStyle={{ fontSize: '15px' }}
floatingLabelText="Email" floatingLabelText='Email'
type="email" type='email'
tabIndex={1} tabIndex={1}
/><br /> /><br />
<TextField <TextField
onChange={this.handleInputChange} onChange={this.handleInputChange}
errorText={this.state.passwordInputError} errorText={this.state.passwordInputError}
name="passwordInput" name='passwordInput'
floatingLabelStyle={{ fontSize: "15px" }} floatingLabelStyle={{ fontSize: '15px' }}
floatingLabelText="Password" floatingLabelText='Password'
type="password" type='password'
tabIndex={2} tabIndex={2}
/><br /> /><br />
<br /> <br />
<br /> <br />
<div className="login__button-box"> <div className='login__button-box'>
<div> <div>
<FlatButton label="Create an account" onClick={this.props.signupPage} tabIndex={4} /> <FlatButton label='Create an account' onClick={this.props.signupPage} tabIndex={4} />
</div> </div>
<div > <div >
<RaisedButton label="Login" primary={true} onClick={this.handleForm} tabIndex={3} /> <RaisedButton label='Login' primary={true} onClick={this.handleForm} tabIndex={3} />
</div> </div>
</div> </div>
@@ -185,13 +182,13 @@ export class Login extends Component {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapDispatchToProps = (dispatch, ownProps) => { const mapDispatchToProps = (dispatch: any, ownProps: ILoginComponentProps) => {
return { return {
login: (username, password) => { login: (email: string, password: string) => {
dispatch(authorizeActions.dbLogin(username, password)) dispatch(authorizeActions.dbLogin(email, password))
}, },
signupPage: () => { signupPage: () => {
dispatch(push("/signup")) dispatch(push('/signup'))
} }
} }
} }
@@ -202,11 +199,11 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state, ownProps) => { const mapStateToProps = (state: any, ownProps: ILoginComponentProps) => {
return { return {
} }
} }
// - Connect component to redux store // - Connect component to redux store
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Login)) export default withRouter(connect(mapStateToProps, mapDispatchToProps)(LoginComponent as any))

View File

@@ -0,0 +1,2 @@
import LoginComponent from './LoginComponent'
export default LoginComponent

View File

@@ -1,4 +1,4 @@
export interface IMasterProps { export interface IMasterComponentProps {
/** /**
* Close gloal message * Close gloal message
* *

View File

@@ -1,24 +1,24 @@
export interface IMasterState { export interface IMasterComponentState {
/** /**
* Loding will be appeared if it's true * Loding will be appeared if it's true
* *
* @type {Boolean} * @type {boolean}
* @memberof IMasterState * @memberof IMasterState
*/ */
loading: Boolean, loading: boolean,
/** /**
* It's true if user is authorized * It's true if user is authorized
* *
* @type {Boolean} * @type {boolean}
* @memberof IMasterState * @memberof IMasterState
*/ */
authed: Boolean authed: boolean
/** /**
* It's true if all default data loaded from database * It's true if all default data loaded from database
* *
* @type {Boolean} * @type {boolean}
* @memberof IMasterState * @memberof IMasterState
*/ */
dataLoaded: Boolean dataLoaded: boolean
} }

View File

@@ -3,22 +3,19 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { Route, Switch, NavLink, withRouter, Redirect } from 'react-router-dom' import { Route, Switch, NavLink, withRouter, Redirect } from 'react-router-dom'
import { firebaseAuth, firebaseRef } from 'app/firebaseClient' import { firebaseAuth, firebaseRef } from 'data/firebaseClient'
import { push } from 'react-router-redux' import { push } from 'react-router-redux'
import Snackbar from 'material-ui/Snackbar' import Snackbar from 'material-ui/Snackbar'
import LinearProgress from 'material-ui/LinearProgress' import LinearProgress from 'material-ui/LinearProgress'
// - Import components // - Import components
import Home from 'components/Home' import Home from 'components/home'
import Signup from 'components/Signup' import Signup from 'components/signup'
import Login from 'components/Login' import Login from 'components/login'
import Settings from 'components/Settings' import Setting from 'components/setting'
import MasterLoading from 'components/MasterLoading' import MasterLoading from 'components/masterLoading'
import { IMasterProps } from './IMasterProps' import { IMasterComponentProps } from './IMasterComponentProps'
import { IMasterState } from './IMasterState' import { IMasterComponentState } from './IMasterComponentState'
// - Import API
import { PrivateRoute, PublicRoute } from 'api/AuthRouterAPI'
// - Import actions // - Import actions
import { import {
@@ -36,11 +33,11 @@ import {
/* ------------------------------------ */ /* ------------------------------------ */
// - Create Master component class // - Create Master component class
export class Master extends Component<IMasterProps, IMasterState> { export class MasterComponent extends Component<IMasterComponentProps, IMasterComponentState> {
static isPrivate = true static isPrivate = true
// Constructor // Constructor
constructor (props: IMasterProps) { constructor (props: IMasterComponentProps) {
super(props) super(props)
this.state = { this.state = {
loading: true, loading: true,
@@ -68,8 +65,9 @@ export class Master extends Component<IMasterProps, IMasterState> {
} }
componentDidCatch (error: any, info: any) { componentDidCatch (error: any, info: any) {
console.log('====================================') console.log('===========Catched by React componentDidCatch==============')
console.log(error, info) console.log(error, info)
alert({error, info})
console.log('====================================') console.log('====================================')
} }
@@ -127,7 +125,7 @@ export class Master extends Component<IMasterProps, IMasterState> {
{(!this.state.loading && (this.props.loaded || this.props.guest)) {(!this.state.loading && (this.props.loaded || this.props.guest))
? (<Switch> ? (<Switch>
<Route path='/signup' component={Signup} /> <Route path='/signup' component={Signup} />
<Route path='/settings' component={Settings} /> <Route path='/settings' component={Setting} />
<Route path='/login' render={() => { <Route path='/login' render={() => {
console.log('this.props.authed: ', this.props.authed, 'this.props: ', this.props) console.log('this.props.authed: ', this.props.authed, 'this.props: ', this.props)
return ( return (
@@ -154,7 +152,7 @@ export class Master extends Component<IMasterProps, IMasterState> {
} }
// - Map dispatch to props // - Map dispatch to props
const mapDispatchToProps = (dispatch: any, ownProps: any) => { const mapDispatchToProps = (dispatch: any, ownProps: IMasterComponentProps) => {
return { return {
loadData: () => { loadData: () => {
@@ -175,6 +173,7 @@ const mapDispatchToProps = (dispatch: any, ownProps: any) => {
dispatch(voteActions.clearAllvotes()) dispatch(voteActions.clearAllvotes())
dispatch(notifyActions.clearAllNotifications()) dispatch(notifyActions.clearAllNotifications())
dispatch(circleActions.clearAllCircles()) dispatch(circleActions.clearAllCircles())
dispatch(globalActions.clearTemp())
}, },
login: (user: any) => { login: (user: any) => {
@@ -216,4 +215,4 @@ const mapStateToProps = (state: any) => {
} }
// - Connect commponent to redux store // - Connect commponent to redux store
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Master as any)) export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MasterComponent as any))

View File

@@ -0,0 +1,2 @@
import MasterComponent from './MasterComponent'
export default MasterComponent

View File

@@ -0,0 +1,12 @@
export interface IMasterLoadingComponentProps {
/**
* Loading is active {true} or not {false}
*
* @type {boolean}
* @memberof IMasterLoadingComponentProps
*/
activeLoading: boolean
handleLoading: (status: boolean) => void
}

View File

@@ -0,0 +1,4 @@
export interface IMasterLoadingComponentState {
}

View File

@@ -0,0 +1,47 @@
// - Import react components
import React, { Component } from 'react'
import CircularProgress from 'material-ui/CircularProgress'
import Dialog from 'material-ui/Dialog'
import { IMasterLoadingComponentProps } from './IMasterLoadingComponentProps'
import { IMasterLoadingComponentState } from './IMasterLoadingComponentState'
// - Import app components
// - Create MasterLoading component class
export default class MasterLoadingComponent extends Component<IMasterLoadingComponentProps,IMasterLoadingComponentState> {
// Constructor
constructor (props: IMasterLoadingComponentProps) {
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>
)
}
}

View File

@@ -0,0 +1,2 @@
import MasterLoadingComponent from './MasterLoadingComponent'
export default MasterLoadingComponent

View File

@@ -0,0 +1,45 @@
import { Profile } from 'core/domain/users'
import { Notification } from 'core/domain/notifications'
export interface INotifyComponentProps {
/**
* Notifications
*
* @type {{[notificationId: string]: Notification}}
* @memberof INotifyComponentProps
*/
notifications?: {[notificationId: string]: Notification}
/**
* Users' profile
*
* @type {{[userId: string]: Profile}}
* @memberof INotifyComponentProps
*/
info?: {[userId: string]: Profile}
/**
* Close notification
*
* @memberof INotifyComponentProps
*/
onRequestClose: () => void
/**
* User notifications popover is opem {true} or not {false}
*
* @type {boolean}
* @memberof INotifyComponentProps
*/
open: boolean
/**
* Keep element
*
* @type {*}
* @memberof INotifyComponentProps
*/
anchorEl: any
}

Some files were not shown because too many files have changed in this diff Show More