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/
public/bundle.js
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",
"keycode": "^2.1.9",
"lodash": "^4.17.4",
"material-ui": "^0.19.3",
"material-ui": "^0.19.4",
"moment": "^2.18.1",
"morgan": "^1.8.1",
"node-env-file": "^0.1.8",
@@ -60,13 +60,16 @@
"@types/lodash": "^4.14.77",
"@types/material-ui": "^0.18.2",
"@types/node": "^8.0.33",
"@types/prop-types": "^15.5.2",
"@types/react": "^16.0.10",
"@types/react-dom": "^16.0.1",
"@types/react-event-listener": "^0.4.4",
"@types/react-redux": "^5.0.10",
"@types/react-router-dom": "^4.0.8",
"@types/react-router-redux": "^5.0.8",
"@types/react-tap-event-plugin": "0.0.30",
"@types/redux-logger": "^3.0.4",
"@types/uuid": "^3.4.3",
"@types/webpack": "^3.0.13",
"babel-core": "^6.24.1",
"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 moment from 'moment'
import { push } from 'react-router-redux'
// -Import domain
import { User } from 'domain/users'
import { SocialError } from 'domain/common'
import { User } from 'core/domain/users'
import { SocialError } from 'core/domain/common'
import { UserRegisterModel } from 'models/users/userRegisterModel'
// - Import action types
import { AuthorizeActionType } from 'constants/authorizeActionType'
// - Import services
import { IAuthorizeService } from 'services/authorize'
import { IServiceProvider, ServiceProvide } from 'factories'
const serviceProvider: IServiceProvider = new ServiceProvide()
const authorizeService: IAuthorizeService = serviceProvider.createAuthorizeService()
import { IAuthorizeService } from 'core/services/authorize'
import { IServiceProvider, ServiceProvide } from 'core/factories'
// - Import actions
import * as globalActions from 'actions/globalActions'
const serviceProvider: IServiceProvider = new ServiceProvide()
const authorizeService: IAuthorizeService = serviceProvider.createAuthorizeService()
/* _____________ CRUD DB _____________ */
/**
@@ -55,10 +58,15 @@ export const dbLogout = () => {
*
* @param user for registering
*/
export const dbSignup = (user: User) => {
return (dispatch: any, getState: any) => {
export const dbSignup = (user: UserRegisterModel) => {
return (dispatch: Function, getState: Function) => {
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({
userId: result.uid,
...user
@@ -123,7 +131,7 @@ export const logout = () => {
* User registeration call
* @param user for registering
*/
export const signup = (user: User) => {
export const signup = (user: UserRegisterModel) => {
return {
type: AuthorizeActionType.SIGNUP,
payload: { ...user }

View File

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

View File

@@ -2,8 +2,8 @@
import moment from 'moment'
// - Import domain
import { Comment } from 'domain/comments'
import { SocialError } from 'domain/common'
import { Comment } from 'core/domain/comments'
import { SocialError } from 'core/domain/common'
// - Import action types
import { CommentActionType } from 'constants/commentActionType'
@@ -12,8 +12,8 @@ import { CommentActionType } from 'constants/commentActionType'
import * as globalActions from 'actions/globalActions'
import * as notifyActions from 'actions/notifyActions'
import { IServiceProvider, ServiceProvide } from 'factories'
import { ICommentService } from 'services/comments'
import { IServiceProvider, ServiceProvide } from 'core/factories'
import { ICommentService } from 'core/services/comments'
const serviceProvider: IServiceProvider = new ServiceProvide()
const commentService: ICommentService = serviceProvider.createCommentService()
@@ -26,7 +26,7 @@ const commentService: ICommentService = serviceProvider.createCommentService()
* @param {object} newComment user comment
* @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) => {
dispatch(globalActions.showTopLoading())
@@ -74,14 +74,9 @@ export const dbGetComments = () => {
return (dispatch: any, getState: Function) => {
let uid: string = getState().authorize.uid
if (uid) {
return commentService.getComments()
.then((comments: {[postId: string]: {[commentId: string]: Comment}}) => {
return commentService.getComments((comments: {[postId: string]: {[commentId: string]: Comment}}) => {
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} 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) => {
if (id === undefined || id === null) {
dispatch(globalActions.showErrorMessage('comment id can not be null or undefined'))
}
console.log('====================================')
console.log(id,postId)
console.log('====================================')
dispatch(globalActions.showTopLoading())
// Get current user id
let uid: string = getState().authorize.uid
return commentService.deleteComment(id,postId)
return commentService.deleteComment(id!,postId!)
.then(() => {
dispatch(deleteComment(id, postId))
dispatch(deleteComment(id!, postId!))
dispatch(globalActions.hideTopLoading())
}, (error: SocialError) => {

View File

@@ -146,7 +146,7 @@ export const hideTopLoading = () => {
/**
* Store temp data
*/
export const temp = (data: string) => {
export const temp = (data: any) => {
return{
type: GlobalActionType.TEMP,
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
export const loadDataGuest = () => {
// tslint:disable-next-line:no-empty

View File

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

View File

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

View File

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

View File

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

View File

@@ -4,14 +4,14 @@ import moment from 'moment'
import { VoteActionType } from 'constants/voteActionType'
// - Import domain
import { Vote } from 'domain/votes'
import { Vote } from 'core/domain/votes'
// - Import actions
import * as globalActions from 'actions/globalActions'
import * as notifyActions from 'actions/notifyActions'
import { IServiceProvider, ServiceProvide } from 'factories'
import { IVoteService } from 'services/votes'
import { IServiceProvider, ServiceProvide } from 'core/factories'
import { IVoteService } from 'core/services/votes'
const serviceProvider: IServiceProvider = new ServiceProvide()
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, { Component } from 'react'
import { connect } from 'react-redux'
import { NavLink} from 'react-router-dom'
import { NavLink } from 'react-router-dom'
import PropTypes from 'prop-types'
import { createAction as action } from 'redux-actions'
import moment from 'moment'
import Linkify from 'react-linkify'
// - Import material UI libraries
import { List, ListItem } from 'material-ui/List'
import Divider from 'material-ui/Divider'
@@ -21,27 +19,24 @@ import MenuItem from 'material-ui/MenuItem'
import TextField from 'material-ui/TextField'
// - Import app components
import UserAvatar from 'UserAvatar'
import UserAvatarComponent from 'components/userAvatar'
// - Import API
// - Import action types
import * as types from 'actionTypes'
import * as types from 'constants/actionTypes'
// - Import actions
import * as commentActions from 'commentActions'
import * as userActions from 'userActions'
import * as commentActions from 'actions/commentActions'
import * as userActions from 'actions/userActions'
import { ICommentComponentProps } from './ICommentComponentProps'
import { ICommentComponentState } from './ICommentComponentState'
/**
* Create component class
*/
export class Comment extends Component {
export class CommentComponent extends Component<ICommentComponentProps,ICommentComponentState> {
static propTypes = {
/**
@@ -55,20 +50,84 @@ export class Comment extends Component {
/**
* 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
* @param {object} props is an object properties of component
*/
constructor(props) {
constructor (props: ICommentComponentProps) {
super(props)
this.textareaRef = i => { this.inputText = i }
this.divCommentRef = i => { this.divComment = i }
this.textareaRef = (i: any) => { this.inputText = i }
this.divCommentRef = (i: any) => { this.divComment = i }
//Defaul state
// Defaul state
this.state = {
/**
* 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
*/
isPostOwner: PropTypes.bool
isPostOwner: false
}
@@ -98,22 +157,20 @@ export class Comment extends Component {
}
/**
* Handle show edit comment
* @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.props.openEditor()
}
/**
* Handle cancel edit
* @param {event} evt is an event passed by clicking on cancel button
*/
handleCancelEdit = (evt) => {
handleCancelEdit = (evt: any) => {
this.setState({
text: this.state.initialText
@@ -125,7 +182,7 @@ export class Comment extends Component {
* Handle edit comment
* @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.setState({
@@ -134,14 +191,12 @@ export class Comment extends Component {
}
/**
* When comment text changed
* @param {event} evt is an event passed by change comment text callback funciton
* @param {string} data is the comment text which user writes
*/
handleOnChange = (evt) => {
handleOnChange = (evt: any) => {
const data = evt.target.value
this.inputText.style.height = evt.target.scrollHeight + 'px'
if (data.length === 0 || data.trim() === '' || data.trim() === this.state.initialText) {
@@ -149,8 +204,7 @@ export class Comment extends Component {
text: data,
editDisabled: true
})
}
else {
} else {
this.setState({
text: data,
editDisabled: false
@@ -159,119 +213,81 @@ export class Comment extends Component {
}
/**
* Delete a comment
* @param {event} evt an event passed by click on delete comment
* @param {string} id comment identifire
* @param {string} postId post identifier which comment belong to
*/
handleDelete = (evt, id, postId) => {
handleDelete = (evt: any, id?: string| null, postId?: string) => {
this.props.delete(id, postId)
}
componentWillMount() {
componentWillMount () {
const {userId} = this.props.comment
if (!this.props.isCommentOwner && !this.props.info[userId]) {
if (!this.props.isCommentOwner && !this.props.info[userId!]) {
this.props.getUserInfo()
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
/**
* DOM styles
*
*
* @memberof Comment
*/
const styles = {
comment: {
marginBottom: '12px'
},
iconButton: {
width: 16,
height: 16
},
author:{
fontSize: "13px",
paddingRight: "10px",
fontWeight: 400,
color: "rgba(0,0,0,0.87)",
textOverflow: "ellipsis",
overflow: "hidden"
},
commentBody:{
fontSize: "13px",
lineHeight: "20px",
color: "rgba(0,0,0,0.87)",
fontWeight: 300,
height: "",
display: "block"
}
}
render () {
/**
* Comment object from props
*/
const {comment} = this.props
const iconButtonElement = (
<IconButton style={styles.iconButton} iconStyle={styles.iconButton}
<IconButton style={this.styles.iconButton} iconStyle={this.styles.iconButton}
touch={true}
>
<MoreVertIcon color={grey400} viewBox='9 0 24 24' />
</IconButton>
)
const RightIconMenu = () => (
<IconMenu iconButtonElement={iconButtonElement} style={{ display: "block", position: "absolute", top: "0px", right: "4px" }}>
<MenuItem style={{ fontSize: "14px" }}>Reply</MenuItem>
{this.props.isCommentOwner ? (<MenuItem style={{ fontSize: "14px" }} onClick={this.handleEditComment}>Edit</MenuItem>): ''}
{(this.props.isCommentOwner || this.props.isPostOwner) ? ( <MenuItem style={{ fontSize: "14px" }} onClick={(evt) => this.handleDelete(evt, comment.id, comment.postId)}>Delete</MenuItem>): ''}
<IconMenu iconButtonElement={iconButtonElement} style={{ display: 'block', position: 'absolute', top: '0px', right: '4px' }}>
<MenuItem style={{ fontSize: '14px' }}>Reply</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>) : ''}
</IconMenu>
)
const Author = () => (
<div style={{ marginTop: "-11px" }}>
<span style={styles.author}>{comment.userDisplayName}</span><span style={{
<div style={{ marginTop: '-11px' }}>
<span style={this.styles.author as any}>{comment.userDisplayName}</span><span style={{
fontWeight: 100,
fontSize: "10px"
}}>{moment.unix(comment.creationDate).fromNow()}</span>
fontSize: '10px'
}}>{moment.unix(comment.creationDate!).fromNow()}</span>
</div>
)
const commentBody = (
<p style={styles.commentBody}>{comment.text}</p>
<p style={this.styles.commentBody as any}>{comment.text}</p>
)
const {userId} = comment
return (
<div className="animate-top" style={styles.comment} key={comment.id}>
<Paper zDepth={0} className="animate2-top10" style={{ position: "relative", padding: "", display: (!this.state.display ? "block" : "none") }}>
<div style={{ marginLeft: "0px", padding: "16px 56px 0px 72px", position: "relative" }}>
<NavLink to={`/${userId}`}><UserAvatar 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>
<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') }}>
<div style={{ marginLeft: '0px', padding: '16px 56px 0px 72px', position: 'relative' }}>
<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>
{(!this.props.isCommentOwner && !this.props.isPostOwner && this.props.disableComments )? '' : (<RightIconMenu />)}
<div style={{ outline: "none", marginLeft: "16px", flex: "auto", flexGrow: 1 }}>
<textarea ref={this.textareaRef} className="animate2-top10" style={{ fontWeight: 100, fontSize: "14px", border: "none", width: "100%", outline: "none", resize: "none", display: (this.props.comment.editorStatus ? 'block' : 'none') }} onChange={this.handleOnChange} value={this.state.text}></textarea>
{(!this.props.isCommentOwner && !this.props.isPostOwner && this.props.disableComments ) ? '' : (<RightIconMenu />)}
<div style={{ outline: 'none', marginLeft: '16px', flex: 'auto', flexGrow: 1 }}>
<textarea ref={this.textareaRef} className='animate2-top10' style={ 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'}}}>
<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>
</div>
</div>
<div style={{ display: (this.props.comment.editorStatus ? "flex" : "none"), flexDirection: "row-reverse" }}>
<FlatButton primary={true} disabled={this.state.editDisabled} label="Update" style={{ float: "right", clear: "both", zIndex: 5, margin: "0px 5px 5px 0px", fontWeight: 400 }} onClick={this.handleUpdateComment} />
<FlatButton primary={true} label="Cancel" style={{ float: "right", clear: "both", zIndex: 5, margin: "0px 5px 5px 0px", fontWeight: 400 }} onClick={this.handleCancelEdit} />
<div style={{ display: (this.props.comment.editorStatus ? 'flex' : 'none'), flexDirection: 'row-reverse' }}>
<FlatButton primary={true} disabled={this.state.editDisabled} label='Update' style={{ float: 'right', clear: 'both', zIndex: 5, margin: '0px 5px 5px 0px', fontWeight: 400 }} onClick={this.handleUpdateComment} />
<FlatButton primary={true} label='Cancel' style={this.styles.cancel as any} onClick={this.handleCancelEdit} />
</div>
</Paper>
@@ -280,20 +296,19 @@ export class Comment extends Component {
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
const mapDispatchToProps = (dispatch: any, ownProps: any) => {
return {
delete: (id, postId) => dispatch(commentActions.dbDeleteComment(id, postId)),
update: (id, postId, comment) => dispatch(commentActions.dbUpdateComment(id, postId, comment)),
delete: (id: string| null, postId: string) => dispatch(commentActions.dbDeleteComment(id, postId)),
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 })),
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
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
const mapStateToProps = (state: any, ownProps: any) => {
const {uid} = state.authorize
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 || '' : ''
@@ -313,10 +328,8 @@ const mapStateToProps = (state, ownProps) => {
info: state.user.info,
avatar,
fullName
}
}
// - 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 actions
import * as commentActions from 'commentActions'
import * as commentActions from 'actions/commentActions'
// - Import app components
import CommentList from 'CommentList'
import CommentWrite from 'CommentWrite'
import UserAvatar from 'UserAvatar'
import CommentListComponent from 'components/CommentList'
import UserAvatarComponent from 'components/userAvatar'
import { ICommentGroupComponentProps } from './ICommentGroupComponentProps'
import { ICommentGroupComponentState } from './ICommentGroupComponentState'
import { Comment } from 'core/domain/comments/comment'
/**
* 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
*/
open: PropTypes.bool,
open: PropTypes.bool,
/**
* If it's true the comment is disable to write
*/
disableComments: PropTypes.bool,
disableComments: PropTypes.bool,
/**
* 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
*/
isPostOwner: PropTypes.bool,
isPostOwner: PropTypes.bool,
/**
* 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
* @param {object} props is an object properties of component
*/
constructor(props) {
constructor (props: ICommentGroupComponentProps) {
super(props)
/**
* Defaul state
*/
this.state = {
commentText: "",
commentText: '',
postDisable: true
}
@@ -72,8 +88,6 @@ static propTypes = {
this.commentList = this.commentList.bind(this)
this.handlePostComment = this.handlePostComment.bind(this)
this.clearCommentWrite = this.clearCommentWrite.bind(this)
}
/**
@@ -90,7 +104,7 @@ static propTypes = {
* Post comment
*/
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 {string} data is the comment text which user writes
*/
handleOnChange = (evt, data) => {
handleOnChange = (evt: any, data: any) => {
this.setState({ commentText: data })
if (data.length === 0 || data.trim() === '') {
this.setState({
commentText: '',
postDisable: true
})
}
else {
} else {
this.setState({
commentText: data,
postDisable: false
@@ -123,48 +136,45 @@ static propTypes = {
let comments = this.props.comments
if (comments) {
let parsedComments = []
let parsedComments: Comment[] = []
Object.keys(comments).slice(0, 3).forEach((commentId) => {
parsedComments.push({
id: commentId,
...comments[commentId]
...comments![commentId]
})
})
if (parsedComments.length === 2) {
parsedComments.push(parsedComments[0])
}
else if (parsedComments.length === 1) {
} else if (parsedComments.length === 1) {
parsedComments.push(parsedComments[0])
parsedComments.push(parsedComments[0])
}
return parsedComments.map((comment, index) => {
const {userInfo} = this.props
const commentAvatar = userInfo && userInfo[comment.userId] ? userInfo[comment.userId].avatar || '' : ''
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" }}
leftAvatar={<UserAvatar fullName={commentFullName} fileName={commentAvatar} style={{ top: "8px" }} size={36} />}
secondaryText={<div style={{ height: "" }}>
const commentAvatar = userInfo && userInfo[comment.userId!] ? userInfo[comment.userId!].avatar || '' : ''
const commentFullName = userInfo && userInfo[comment.userId!] ? userInfo[comment.userId!].fullName || '' : ''
return (<ListItem key={index} style={this.styles.commentItem as any} innerDivStyle={{ padding: '6px 16px 16px 72px' }}
leftAvatar={<UserAvatarComponent fullName={commentFullName} fileName={commentAvatar} style={{ top: '8px' }} size={36} />}
secondaryText={<div style={{ height: '' }}>
<span style={{
fontSize: "13px",
paddingRight: "10px",
fontSize: '13px',
paddingRight: '10px',
fontWeight: 400,
color: "rgba(0,0,0,0.87)",
textOverflow: "ellipsis",
overflow: "hidden"
color: 'rgba(0,0,0,0.87)',
textOverflow: 'ellipsis',
overflow: 'hidden'
}}>
{comment.userDisplayName}:
</span>
<span style={{
fontSize: "13px",
lineHeight: "20px",
color: "rgba(0,0,0,0.87)",
fontSize: '13px',
lineHeight: '20px',
color: 'rgba(0,0,0,0.87)',
fontWeight: 300,
whiteSpace: "pre-wrap"
whiteSpace: 'pre-wrap'
}}>{comment.text}</span>
</div>}
secondaryTextLines={2}
/>
@@ -180,55 +190,53 @@ static propTypes = {
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
render () {
return (
<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 />
<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" }} >
<FlatButton label=" " style={{ height: "60px", zIndex: 5 }} fullWidth={true} onClick={this.props.onToggleRequest} />
<div style={{ position: 'relative', height: '60px' }} >
<FlatButton label=' ' style={this.styles.toggleShowList} fullWidth={true} onClick={this.props.onToggleRequest} />
<div className="comment__list-show">
<div className='comment__list-show'>
{this.commentList()}
</div>
</div>
</Paper>
{(this.props.comments && Object.keys(this.props.comments).length > 0)
? (<Paper zDepth={0} style={this.props.open ? { display: "block", padding: "0px 0px" } : { display: "none", padding: "12px 16px" }}>
<CommentList comments={this.props.comments} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/>
? (<Paper zDepth={0} style={this.props.open ? { display: 'block', padding: '0px 0px' } : { display: 'none', padding: '12px 16px' }}>
<CommentListComponent comments={this.props.comments} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/>
</Paper>) : ''}
</div>
{!this.props.disableComments ? (<div>
<Divider />
<Paper zDepth={0} className="animate2-top10" style={{ position: "relative", overflowY: "auto", padding: "12px 16px", display: (this.props.open ? "block" : "none") }}>
<Paper zDepth={0} className='animate2-top10' style={{ position: 'relative', overflowY: 'auto', padding: '12px 16px', display: (this.props.open ? 'block' : 'none') }}>
<div style={{ display: "flex" }}>
<UserAvatar 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={{ display: 'flex' }}>
<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 }}>
<TextField
value={this.state.commentText}
onChange={this.handleOnChange}
hintText="Add a comment..."
hintText='Add a comment...'
underlineShow={false}
multiLine={true}
rows={1}
hintStyle={{ fontWeight: 100, fontSize: "14px" }}
hintStyle={{ fontWeight: 100, fontSize: '14px' }}
rowsMax={4}
textareaStyle={{ fontWeight: 100, fontSize: "14px" }}
style={{ width: '100%' }}
textareaStyle={{ fontWeight: 100, fontSize: '14px' }}
style={this.styles.writeCommentTextField}
/>
</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>
</div>): ''}
</div>) : ''}
</div>
)
}
@@ -240,9 +248,9 @@ static propTypes = {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
const mapDispatchToProps = (dispatch: any, ownProps: ICommentGroupComponentProps) => {
return {
send: (text, postId, callBack) => {
send: (text: string, postId: string, callBack: Function) => {
dispatch(commentActions.dbAddComment(ownProps.ownerPostUserId,{
postId: postId,
text: text
@@ -257,15 +265,15 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
const mapStateToProps = (state: any, ownProps: ICommentGroupComponentProps) => {
return {
comments: state.comment.postComments[ownProps.postId],
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 || '' : '',
userInfo: state.user.info
userInfo: state.user.info
}
}
// - 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 { List, ListItem } from 'material-ui/List'
// - Import app components
import Comment from 'Comment'
import * as PostAPI from 'PostAPI'
import CommentComponent from 'components/Comment'
import * as PostAPI from 'api/PostAPI'
import { ICommentListComponentProps } from './ICommentListComponentProps'
import { ICommentListComponentState } from './ICommentListComponentState'
import { Comment } from 'core/domain/comments'
// - Import actions
/**
* 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
*/
@@ -29,14 +28,14 @@ export class CommentList extends Component {
/**
* If it's true the comment is disable to write
*/
disableComments: PropTypes.bool,
}
disableComments: PropTypes.bool
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
constructor (props: ICommentListComponentProps) {
super(props)
/**
@@ -50,8 +49,6 @@ export class CommentList extends Component {
}
/**
* Get comments' DOM
* @return {DOM} list of comments' DOM
@@ -60,8 +57,7 @@ export class CommentList extends Component {
let comments = this.props.comments
if (comments) {
let parsedComments = []
let parsedComments: Comment[] = []
Object.keys(comments).forEach((commentId) => {
parsedComments.push({
id: commentId,
@@ -69,10 +65,10 @@ export class CommentList extends Component {
})
})
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
* @return {react element} return the DOM which rendered by component
*/
render() {
render () {
const styles = {
const styles: any = {
list: {
width: "100%",
width: '100%',
maxHeight: 450,
overflowY: 'auto'
}
@@ -95,7 +91,6 @@ export class CommentList extends Component {
return (
<List style={styles.list}>
{this.commentList()}
@@ -110,10 +105,9 @@ export class CommentList extends Component {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
const mapDispatchToProps = (dispatch: any, ownProps: ICommentListComponentProps) => {
return {
}
}
@@ -123,11 +117,11 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state) => {
const mapStateToProps = (state: any) => {
return {
}
}
// - 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, { Component } from 'react'
import { connect } from 'react-redux'
@@ -18,316 +16,296 @@ import Divider from 'material-ui/Divider'
import Paper from 'material-ui/Paper'
import TextField from 'material-ui/TextField'
// - Import app components
import ImgCover from 'ImgCover'
import DialogTitle from 'DialogTitle'
import ImageGallery from 'ImageGallery'
import FileAPI from 'FileAPI'
import UserAvatar from 'UserAvatar'
import ImgCover from 'components/imgCover'
import UserAvatarComponent from 'components/userAvatar'
import ImageGallery from 'components/imageGallery'
import DialogTitle from 'layouts/DialogTitle'
// - Import API
import FileAPI from 'api/FileAPI'
// - Import actions
import * as userActions from 'userActions'
import * as globalActions from 'globalActions'
import * as imageGalleryActions from 'imageGalleryActions'
import * as userActions from 'actions/userActions'
import * as globalActions from 'actions/globalActions'
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
*/
avatar: PropTypes.string,
avatar: PropTypes.string,
/**
* User avatar address
*/
banner: PropTypes.string,
banner: PropTypes.string,
/**
* 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
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
//Defaul state
this.state = {
constructor (props: IEditProfileComponentProps) {
super(props)
// Defaul state
this.state = {
/**
* If it's true the winow is in small size
*/
isSmall: false,
isSmall: false,
/**
* User tag line input value
*/
tagLineInput: props.info.tagLine || '',
tagLineInput: props.info!.tagLine || '',
/**
* User full name input value
*/
fullNameInput: props.info.fullName || '',
fullNameInput: props.info!.fullName || '',
/**
* Error message of full name input
*/
fullNameInputError: '',
fullNameInputError: '',
/**
* 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
*/
avatar: this.props.avatar || '',
avatar: props.avatar || '',
/**
* 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
*/
openAvatar: false
openAvatar: false
}
}
// Binding functions to `this`
this.handleChangeDate = this.handleChangeDate.bind(this)
this.handleUpdate = this.handleUpdate.bind(this)
this.handleRequestSetAvatar = this.handleRequestSetAvatar.bind(this)
this.handleRequestSetBanner = this.handleRequestSetBanner.bind(this)
this.handleUpdate = this.handleUpdate.bind(this)
this.handleRequestSetAvatar = this.handleRequestSetAvatar.bind(this)
this.handleRequestSetBanner = this.handleRequestSetBanner.bind(this)
}
}
/**
/**
* Close image gallery of banner
*/
handleCloseBannerGallery = () => {
this.setState({
openBanner: false
})
}
handleCloseBannerGallery = () => {
this.setState({
openBanner: false
})
}
/**
/**
* Open image gallery of banner
*/
handleOpenBannerGallery = () => {
this.setState({
openBanner: true
})
}
handleOpenBannerGallery = () => {
this.setState({
openBanner: true
})
}
/**
* Close image gallery of avatar
*/
handleCloseAvatarGallery = () => {
this.setState({
openAvatar: false
})
}
handleCloseAvatarGallery = () => {
this.setState({
openAvatar: false
})
}
/**
* Open image gallery of avatar
*/
handleOpenAvatarGallery = () => {
this.setState({
openAvatar: true
})
}
handleOpenAvatarGallery = () => {
this.setState({
openAvatar: true
})
}
/**
/**
* Set banner image url
*/
handleRequestSetBanner = (url) => {
console.log('==========Banner==================')
console.log(url)
console.log('====================================')
this.setState({
banner: url
})
}
handleRequestSetBanner = (url: string) => {
this.setState({
banner: url
})
}
/**
/**
* Set avatar image url
*/
handleRequestSetAvatar = (fileName) => {
this.setState({
avatar: fileName
})
}
handleRequestSetAvatar = (fileName: string) => {
this.setState({
avatar: fileName
})
}
/**
* Update profile on the server
*
*
*
*
* @memberof EditProfile
*/
handleUpdate = () => {
const {fullNameInput, tagLineInput, avatar, banner} = this.state
if (this.state.fullNameInput.trim() === '') {
this.setState({
fullNameInputError: 'This field is required'
})
}
else {
this.setState({
fullNameInputError: ''
})
handleUpdate = () => {
const {fullNameInput, tagLineInput, avatar, banner} = this.state
this.props.update({
fullName: fullNameInput,
tagLine: tagLineInput,
avatar: avatar,
banner: banner
})
}
if (this.state.fullNameInput.trim() === '') {
this.setState({
fullNameInputError: 'This field is required'
})
} else {
this.setState({
fullNameInputError: ''
})
this.props.update!({
fullName: fullNameInput,
tagLine: tagLineInput,
avatar: avatar,
banner: banner
})
}
}
/**
* Handle data on input change
* @param {event} evt is an event of inputs of element on change
*/
handleInputChange = (evt) => {
const target = evt.target
const value = target.type === 'checkbox' ? target.checked : target.value
const name = target.name
this.setState({
[name]: value
})
}
/**
* Handle change date
*/
handleChangeDate = (evt, date) => {
this.setState({
birthdayInput: date,
})
}
handleInputChange = (event: any) => {
const target = event.target
const value = target.type === 'checkbox' ? target.checked : target.value
const name = target.name
this.setState({
[name]: value
})
}
/**
* Handle resize event for window to change sidebar status
* @param {event} evt is the event is passed by winodw resize event
*/
handleResize = (evt) => {
/**
* Handle resize event for window to change sidebar status
* @param {any} event is the event is passed by winodw resize event
*/
handleResize = (event: any) => {
// Set initial state
let width = window.innerWidth
let width = window.innerWidth
if (width > 900) {
this.setState({
isSmall: false
})
if (width > 900) {
this.setState({
isSmall: false
})
}
else {
this.setState({
isSmall: true
})
}
}
componentDidMount = () => {
this.handleResize()
} else {
this.setState({
isSmall: true
})
}
}
componentDidMount () {
this.handleResize(null)
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
render () {
const styles = {
avatar: {
border: '2px solid rgb(255, 255, 255)'
},
paper: {
width: '90%',
height: '100%',
margin: '0 auto',
display: 'block',
},
title: {
padding: '24px 24px 20px 24px',
font: '500 20px Roboto,RobotoDraft,Helvetica,Arial,sans-serif',
display: 'flex',
wordWrap: 'break-word',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
overflow: 'hidden',
flexGrow: 1
},
actions: {
display: 'flex',
justifyContent: "flex-end",
padding: '24px 24px 20px'
},
updateButton: {
marginLeft: '10px'
},
box: {
padding: '0px 24px 20px 24px',
display: 'flex'
},
dialogGallery: {
width: '',
maxWidth: '530px',
borderRadius: "4px"
}
}
const iconButtonElement = (
<IconButton style={this.state.isSmall ? styles.iconButtonSmall : styles.iconButton} iconStyle={this.state.isSmall ? styles.iconButtonSmall : styles.iconButton}
const iconButtonElement = (
<IconButton style={this.state.isSmall ? this.styles.iconButtonSmall : this.styles.iconButton} iconStyle={this.state.isSmall ? this.styles.iconButtonSmall : this.styles.iconButton}
touch={true}
>
<MoreVertIcon color={grey400} viewBox='10 0 24 24' />
</IconButton>
)
const RightIconMenu = () => (
const RightIconMenu = () => (
<IconMenu iconButtonElement={iconButtonElement}>
<MenuItem style={{ fontSize: "14px" }}>Reply</MenuItem>
<MenuItem style={{ fontSize: "14px" }}>Edit</MenuItem>
<MenuItem style={{ fontSize: "14px" }}>Delete</MenuItem>
<MenuItem style={{ fontSize: '14px' }}>Reply</MenuItem>
<MenuItem style={{ fontSize: '14px' }}>Edit</MenuItem>
<MenuItem style={{ fontSize: '14px' }}>Delete</MenuItem>
</IconMenu>
)
return (
return (
<div>
{/* Edit profile dialog */}
<Dialog
id='Edit-Profile'
key='Edit-Profile'
modal={false}
open={this.props.open}
open={this.props.open!}
onRequestClose={this.props.onRequestClose}
autoScrollBodyContent={true}
bodyStyle={{ backgroundColor: "none", padding: 'none', borderTop: 'none', borderBottom: 'none' }}
overlayStyle={{ background: "rgba(0,0,0,0.12)" }}
contentStyle={{ backgroundColor: "none", maxWidth: '450px', maxHeight: 'none', height: '100%' }}
style={{ backgroundColor: "none", maxHeight: 'none', height: '100%' }}
bodyStyle={{ backgroundColor: 'none', padding: 'none', borderTop: 'none', borderBottom: 'none' }}
overlayStyle={{ background: 'rgba(0,0,0,0.12)' }}
contentStyle={{ backgroundColor: 'none', maxWidth: '450px', maxHeight: 'none', height: '100%' }}
style={{ backgroundColor: 'none', maxHeight: 'none', height: '100%' }}
>
{/* Banner */}
<div style={{ position: 'relative' }}>
@@ -338,7 +316,7 @@ export class EditProfile extends Component {
</div>
<div className='profile__edit'>
<EventListener
target="window"
target='window'
onResize={this.handleResize}
/>
<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)' }} />
</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 className='info'>
<div className='fullName'>
{this.props.fullName}
</div>
</div>
</div>
</div>
{/* Edit user information box*/}
<Paper style={styles.paper} zDepth={1}>
<div style={styles.title}>Personal Information</div>
<div style={styles.box}>
<Paper style={this.styles.paper} zDepth={1}>
<div style={this.styles.title as any}>Personal Information</div>
<div style={this.styles.box}>
<TextField
floatingLabelText="Full name"
floatingLabelText='Full name'
onChange={this.handleInputChange}
name='fullNameInput'
errorText={this.state.fullNameInputError}
@@ -374,23 +351,22 @@ export class EditProfile extends Component {
/>
</div>
<br />
<div style={styles.box}>
<div style={this.styles.box}>
<TextField
floatingLabelText="Tag Line"
floatingLabelText='Tag Line'
onChange={this.handleInputChange}
name='tagLineInput'
value={this.state.tagLineInput}
/>
</div>
<br />
<div style={styles.actions}>
<FlatButton label="CANCEL" onClick={this.props.onRequestClose} />
<RaisedButton label="UPDATE" primary={true} onClick={this.handleUpdate} style={styles.updateButton} />
<div style={this.styles.actions as any}>
<FlatButton label='CANCEL' onClick={this.props.onRequestClose} />
<RaisedButton label='UPDATE' primary={true} onClick={this.handleUpdate} style={this.styles.updateButton} />
</div>
</Paper>
<div style={{ height: '16px' }}></div>
</Dialog>
{/* Image gallery for banner*/}
@@ -398,9 +374,9 @@ export class EditProfile extends Component {
title={<DialogTitle title='Choose an banner image' onRequestClose={this.handleCloseBannerGallery} />}
modal={false}
open={this.state.openBanner}
contentStyle={styles.dialogGallery}
contentStyle={this.styles.dialogGallery}
onRequestClose={this.handleCloseBannerGallery}
overlayStyle={{ background: "rgba(0,0,0,0.12)" }}
overlayStyle={{ background: 'rgba(0,0,0,0.12)' }}
autoDetectWindowHeight={false}
>
@@ -412,9 +388,9 @@ export class EditProfile extends Component {
title={<DialogTitle title='Choose an avatar image' onRequestClose={this.handleCloseAvatarGallery} />}
modal={false}
open={this.state.openAvatar}
contentStyle={styles.dialogGallery}
contentStyle={this.styles.dialogGallery}
onRequestClose={this.handleCloseAvatarGallery}
overlayStyle={{ background: "rgba(0,0,0,0.12)" }}
overlayStyle={{ background: 'rgba(0,0,0,0.12)' }}
autoDetectWindowHeight={false}
>
@@ -422,23 +398,22 @@ export class EditProfile extends Component {
</Dialog>
</div>
)
}
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
update: (info) => dispatch(userActions.dbUpdateUserInfo(info)),
onRequestClose: () => dispatch(userActions.closeEditProfile())
const mapDispatchToProps = (dispatch: any, ownProps: IEditProfileComponentProps) => {
return {
update: (info: Profile) => dispatch(userActions.dbUpdateUserInfo(info)),
onRequestClose: () => dispatch(userActions.closeEditProfile())
}
}
}
/**
@@ -447,14 +422,14 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
open: state.user.openEditProfile,
info: state.user.info[state.authorize.uid],
avatarURL: state.imageGallery.imageURLList
const mapStateToProps = (state: any, ownProps: IEditProfileComponentProps) => {
return {
open: state.user.openEditProfile,
info: state.user.info[state.authorize.uid],
avatarURL: state.imageGallery.imageURLList
}
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(EditProfile)
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 Paper from 'material-ui/Paper'
// - Import app components
import UserBoxList from 'UserBoxList'
import UserBoxList from 'components/userBoxList'
// - Import API
// - 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
* @param {object} props is an object properties of component
*/
constructor(props) {
super(props)
constructor (props: IFindPeopleComponentProps) {
super(props)
//Defaul state
this.state = {
// Defaul state
this.state = {
}
}
// Binding functions to `this`
}
}
componentWillMount() {
this.props.loadPeople()
}
componentWillMount () {
this.props.loadPeople!()
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
render () {
const styles = {
paper: {
height: 254,
width: 243,
margin: 10,
textAlign: 'center',
maxWidth: '257px'
},
followButton:{
position: 'absolute',
bottom: '8px',
left: 0,
right: 0
}
}
const styles = {
paper: {
height: 254,
width: 243,
margin: 10,
textAlign: 'center',
maxWidth: '257px'
},
followButton: {
position: 'absolute',
bottom: '8px',
left: 0,
right: 0
}
}
return (
return (
<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'>
Suggestions for you
</div>
@@ -77,21 +76,20 @@ export class FindPeople extends Component {
Nothing to show! :(
</div>)}
</div>
)
}
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
return {
loadPeople: () => dispatch(userActions.dbGetPeopleInfo())
}
const mapDispatchToProps = (dispatch: any, ownProps: IFindPeopleComponentProps) => {
return {
loadPeople: () => dispatch(userActions.dbGetPeopleInfo())
}
}
/**
@@ -100,11 +98,11 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
return {
peopleInfo: state.user.info
}
const mapStateToProps = (state: any, ownProps: IFindPeopleComponentProps) => {
return {
peopleInfo: state.user.info
}
}
// - 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, {Component} from 'react'
import {connect} from 'react-redux'
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
// - Import app components
import UserBoxList from 'UserBoxList'
import UserBoxList from 'components/userBoxList'
import { IFollowersComponentProps } from './IFollowersComponentProps'
import { IFollowersComponentState } from './IFollowersComponentState'
// - Import API
// - 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
* @param {object} props is an object properties of component
*/
constructor(props){
constructor (props: IFollowersComponentProps) {
super(props)
//Defaul state
// Defaul state
this.state = {
}
@@ -40,24 +42,23 @@ static propTypes = {
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
return (
render () {
return (
<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'>
Followers
</div>
<UserBoxList users={this.props.followers} />
<div style={{ height: '24px' }}></div>
</div>)
</div>)
: (<div className='g__title-center'>
No followers!
</div>)}
</div>
)
}
}
)
}
}
/**
* Map dispatch to props
@@ -65,11 +66,11 @@ static propTypes = {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch,ownProps) => {
return{
const mapDispatchToProps = (dispatch: any,ownProps: IFollowersComponentProps) => {
return{
}
}
}
/**
* Map state to props
@@ -77,13 +78,13 @@ static propTypes = {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state,ownProps) => {
const { uid } = state.authorize
const circles = state.circle ? state.circle.userCircles[uid] : {}
return{
followers: circles ? (circles['-Followers'] ? circles['-Followers'].users || {} : {}) : {}
}
const mapStateToProps = (state: any,ownProps: IFollowersComponentProps) => {
const { uid } = state.authorize
const circles = state.circle ? state.circle.userCircles[uid] : {}
return{
followers: circles ? (circles['-Followers'] ? circles['-Followers'].users || {} : {}) : {}
}
}
// - Connect component to redux store
export default connect(mapStateToProps,mapDispatchToProps)(Followers)
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, {Component} from 'react'
import {connect} from 'react-redux'
import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
// - Import app components
import UserBoxList from 'UserBoxList'
import UserBoxList from 'components/userBoxList'
// - Import API
import CircleAPI from 'CircleAPI'
import CircleAPI from 'api/CircleAPI'
import { IFollowingComponentProps } from './IFollowingComponentProps'
import { IFollowingComponentState } from './IFollowingComponentState'
// - 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
* @param {object} props is an object properties of component
*/
constructor(props){
constructor (props: IFollowingComponentProps) {
super(props)
//Defaul state
// Defaul state
this.state = {
}
@@ -41,25 +42,24 @@ static propTypes = {
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
return (
render () {
return (
<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'>
Following
</div>
<UserBoxList users={this.props.followingUsers} />
<div style={{ height: '24px' }}></div>
</div>) : (<div className='g__title-center'>
No following user!
</div>)}
</div>
)
}
}
)
}
}
/**
* Map dispatch to props
@@ -67,11 +67,11 @@ static propTypes = {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch,ownProps) => {
return{
const mapDispatchToProps = (dispatch: any,ownProp: IFollowingComponentProps) => {
return{
}
}
}
/**
* Map state to props
@@ -79,17 +79,17 @@ static propTypes = {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state,ownProps) => {
const mapStateToProps = (state: any,ownProps: IFollowingComponentProps) => {
const { uid } = state.authorize
const circles = state.circle ? state.circle.userCircles[uid] : {}
const followingUsers = CircleAPI.getFollowingUsers(circles)
return {
uid,
circles,
followingUsers,
}
followingUsers
}
}
// - 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 app components
import Sidebar from 'Sidebar'
import Blog from 'Blog'
import HomeHeader from 'HomeHeader'
import SidebarContent from 'SidebarContent'
import SidebarMain from 'SidebarMain'
import Profile from 'Profile'
import PostPage from 'PostPage'
import People from 'People'
import Sidebar from 'components/sidebar'
import StreamComponent from 'components/stream'
import HomeHeader from 'components/homeHeader'
import SidebarContent from 'components/sidebarContent'
import SidebarMain from 'components/sidebarMain'
import Profile from 'components/profile'
import PostPage from 'components/postPage'
import People from 'components/people'
// - Import API
import CircleAPI from 'CircleAPI'
import CircleAPI from 'api/CircleAPI'
// - 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
export class Home extends Component {
export class HomeComponent extends Component<IHomeComponentProps, IHomeComponentState> {
// Constructor
constructor(props) {
constructor (props: IHomeComponentProps) {
super(props)
// Default state
this.state = {
sidebarOpen: () => _,
sidebarStatus: true,
sidebaOverlay: false
sidebarOverlay: false
}
// Binding function to `this`
@@ -57,37 +59,35 @@ export class Home extends Component {
* handle close sidebar
*/
handleCloseSidebar = () => {
this.state.sidebarOpen(false, 'overlay')
this.state.sidebarOpen!(false, 'overlay')
}
/**
* Change sidebar overlay status
* @param {boolean} status if is true, the sidebar is on overlay status
*/
sidebarOverlay = (status) => {
sidebarOverlay = (status: boolean) => {
this.setState({
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
*/
sidebar = (open) => {
sidebar = (open: (status: boolean, source: string) => void) => {
this.setState({
sidebarOpen: open
})
}
/**
* Change sidebar status if is open or not
* @param {boolean} status is true, if the sidebar is open
*/
sidebarStatus = (status) => {
sidebarStatus = (status: boolean) => {
this.setState({
sidebarStatus: status
})
@@ -95,60 +95,59 @@ export class Home extends Component {
/**
* Render DOM component
*
*
* @returns DOM
*
*
* @memberof Home
*/
render() {
render () {
return (
<div id="home">
<div id='home'>
<HomeHeader sidebar={this.state.sidebarOpen} sidebarStatus={this.state.sidebarStatus} />
<Sidebar overlay={this.sidebarOverlay} open={this.sidebar} status={this.sidebarStatus}>
<SidebarContent>
<Menu style={{ color: "rgb(117, 117, 117)", width: '210px' }}>
<Menu style={{ color: 'rgb(117, 117, 117)', width: '210px' }}>
{this.state.sidebarOverlay
? <div><MenuItem onClick={this.handleCloseSidebar} primaryText={<span style={{ color: "rgb(117, 117, 117)" }} className="sidebar__title">Green</span>} rightIcon={<SvgArrowLeft viewBox="0 3 24 24" style={{ color: "#fff", marginLeft: "15px", width: "32px", height: "32px", cursor: "pointer" }} />} /><Divider /></div>
: ""
? <div><MenuItem onClick={this.handleCloseSidebar} primaryText={<span style={{ color: 'rgb(117, 117, 117)' }} className='sidebar__title'>Green</span>} rightIcon={<SvgArrowLeft viewBox='0 3 24 24' style={{ color: '#fff', marginLeft: '15px', width: '32px', height: '32px', cursor: 'pointer' }} />} /><Divider /></div>
: ''
}
<NavLink to='/'><MenuItem primaryText="Home" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgHome />} /></NavLink>
<NavLink to={`/${this.props.uid}`}><MenuItem primaryText="Profile" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgAccountCircle />} /></NavLink>
<NavLink to='/people'><MenuItem primaryText="People" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgPeople />} /></NavLink>
<NavLink to='/'><MenuItem primaryText='Home' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgHome />} /></NavLink>
<NavLink to={`/${this.props.uid}`}><MenuItem primaryText='Profile' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgAccountCircle />} /></NavLink>
<NavLink to='/people'><MenuItem primaryText='People' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgPeople />} /></NavLink>
<Divider />
<NavLink to='/settings'><MenuItem primaryText="Settings" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgSettings />} /></NavLink>
<NavLink to='#'><MenuItem primaryText="Send feedback" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgFeedback />} /></NavLink>
<NavLink to='/settings'><MenuItem primaryText='Settings' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgSettings />} /></NavLink>
<NavLink to='#'><MenuItem primaryText='Send feedback' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgFeedback />} /></NavLink>
</Menu>
</SidebarContent>
<SidebarMain>
<Switch>
<Route path="/people/:tab?" render={() => {
<Route path='/people/:tab?' render={() => {
return (
this.props.authed
? <People />
: <Redirect to="/login" />
: <Redirect to='/login' />
)
}} />
<Route path="/tag/:tag" render={({match}) => {
<Route path='/tag/:tag' render={({match}) => {
return (
this.props.authed
? <div className="blog"><Blog displayWriting={false} homeTitle={`#${match.params.tag}`} posts={this.props.mergedPosts} /></div>
: <Redirect to="/login" />
? <div className='blog'><StreamComponent displayWriting={false} homeTitle={`#${match.params.tag}`} posts={this.props.mergedPosts} /></div>
: <Redirect to='/login' />
)
}} />
<Route path="/:userId/posts/:postId/:tag?" component={PostPage} />
<Route path="/:userId" component={Profile} />
<Route path='/:userId/posts/:postId/:tag?' component={PostPage} />
<Route path='/:userId' component={Profile} />
<Route path="/" render={() => {
<Route path='/' render={() => {
return (
this.props.authed
? <div className="blog"><Blog homeTitle='Home' posts={this.props.mergedPosts} displayWriting={true} /></div>
: <Redirect to="/login" />
? <div className='blog'><StreamComponent homeTitle='Home' posts={this.props.mergedPosts} displayWriting={true} /></div>
: <Redirect to='/login' />
)
}} />
</Switch>
@@ -167,10 +166,9 @@ export class Home extends Component {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
const mapDispatchToProps = (dispatch: any, ownProps: IHomeComponentProps) => {
return {
}
}
@@ -180,15 +178,15 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
const mapStateToProps = (state: any, ownProps: IHomeComponentProps) => {
const { uid } = state.authorize
let mergedPosts = {}
const circles = state.circle ? (state.circle.userCircles[uid] || {}) : {}
const followingUsers = CircleAPI.getFollowingUsers(circles)
const posts = state.post.userPosts ? state.post.userPosts[state.authorize.uid] : {}
Object.keys(followingUsers).forEach((userId)=>{
let newPosts = state.post.userPosts ? state.post.userPosts[userId] : {}
_.merge(mergedPosts,newPosts)
Object.keys(followingUsers).forEach((userId) => {
let newPosts = state.post.userPosts ? state.post.userPosts[userId] : {}
_.merge(mergedPosts,newPosts)
})
_.merge(mergedPosts,posts)
return {
@@ -199,4 +197,4 @@ const mapStateToProps = (state, ownProps) => {
}
// - 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 EventListener, { withOptions } from 'react-event-listener'
// - Import components
import UserAvatar from 'UserAvatar'
import Notify from 'Notify'
import UserAvatarComponent from 'components/userAvatar'
import Notify from 'components/notify'
// - Import actions
import * as globalActions from 'globalActions'
import * as authorizeActions from 'authorizeActions'
import * as globalActions from 'actions/globalActions'
import { authorizeActions } from 'actions'
import { IHomeHeaderComponentProps } from './IHomeHeaderComponentProps'
import { IHomeHeaderComponentState } from './IHomeHeaderComponentState'
// - 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
* @param {object} props is an object properties of component
*/
constructor(props) {
constructor (props: IHomeHeaderComponentProps) {
super(props)
// Default state
@@ -56,12 +75,10 @@ export class HomeHeader extends Component {
}
/**
* Handle close notification menu
*
*
* Handle close notification menu
*
*
* @memberof HomeHeader
*/
handleCloseNotify = () => {
@@ -70,78 +87,80 @@ export class HomeHeader extends Component {
})
}
// On click toggle sidebar
onToggleSidebar = () => {
if (this.props.sidebarStatus) {
this.props.sidebar(false)
this.props.sidebar!(false,'onToggle')
} else {
this.props.sidebar(true)
this.props.sidebar!(true,'onToggle')
}
}
/**
* Handle notification touch
*
*
* Handle notification touch
*
*
* @memberof HomeHeader
*/
handleNotifyTouchTap = (event) => {
handleNotifyTouchTap = (event: any) => {
// This prevents ghost click.
event.preventDefault()
this.setState({
openNotifyMenu: true,
anchorEl: event.currentTarget,
anchorEl: event.currentTarget
})
}
/**
* Handle touch on user avatar for popover
*
*
*
*
* @memberof HomeHeader
*/
handleAvatarTouchTap = (event) => {
handleAvatarTouchTap = (event: any) => {
// This prevents ghost click.
event.preventDefault()
this.setState({
openAvatarMenu: true,
anchorEl: event.currentTarget,
anchorEl: event.currentTarget
})
}
/**
* Handle logout user
*
*
*
*
* @memberof HomeHeader
*/
handleLogout = () => {
this.props.logout()
this.props.logout!()
}
/**
* Handle close popover
*
*
*
*
* @memberof HomeHeader
*/
handleRequestClose = () => {
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
* @param {event} evt is the event is passed by winodw resize event
*/
handleResize = (evt) => {
handleResize = (event: any) => {
// Set initial state
let width = window.innerWidth
@@ -151,8 +170,7 @@ export class HomeHeader extends Component {
showTitle: true
})
}
else if (width < 600 && this.state.showTitle) {
} else if (width < 600 && this.state.showTitle) {
this.setState({
showTitle: false
@@ -160,54 +178,30 @@ export class HomeHeader extends Component {
}
}
componentDidMount = () => {
this.handleResize()
componentDidMount () {
this.handleResize(null)
}
// Render app DOM component
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'
}
}
render () {
return (
<Toolbar style={styles.toolbarStyle} className="g__greenBox">
<Toolbar style={this.styles.toolbarStyle as any} className='g__greenBox'>
<EventListener
target="window"
target='window'
onResize={this.handleResize}
onKeyUp={this.handleKeyUp}
/>
{/* Left side */}
<ToolbarGroup firstChild={true}>
<IconButton iconStyle={{ color: "#fff" }} onClick={this.onToggleSidebar} >
<SvgDehaze style={{ color: "#fff", marginLeft: "15px", cursor: "pointer" }} />
<IconButton iconStyle={{ color: '#fff' }} onClick={this.onToggleSidebar} >
<SvgDehaze style={{ color: '#fff', marginLeft: '15px', cursor: 'pointer' }} />
</IconButton>
{/* Header title */}
<ToolbarTitle style={{ color: "#fff", marginLeft: "15px" }} text="Green" />
{this.state.showTitle ? <div className="homeHeader__page">{this.props.title}</div> : ''}
<ToolbarTitle style={{ color: '#fff', marginLeft: '15px' }} text='Green' />
{this.state.showTitle ? <div className='homeHeader__page'>{this.props.title}</div> : ''}
</ToolbarGroup>
<ToolbarGroup>
@@ -215,26 +209,25 @@ export class HomeHeader extends Component {
{/* Notification */}
<ToolbarGroup lastChild={true}>
<div className="homeHeader__right">
{this.props.notifyCount > 0 ? (<IconButton tooltip="Notifications" onTouchTap={this.handleNotifyTouchTap}>
<div className="homeHeader__notify">
<div className='homeHeader__right'>
{this.props.notifyCount! > 0 ? (<IconButton tooltip='Notifications' onTouchTap={this.handleNotifyTouchTap}>
<div className='homeHeader__notify'>
<div className='title'>{this.props.notifyCount}</div>
</div>
</IconButton>)
: (<IconButton tooltip="Notifications" onTouchTap={this.handleNotifyTouchTap}>
: (<IconButton tooltip='Notifications' onTouchTap={this.handleNotifyTouchTap}>
<NotificationsIcon color='rgba(255, 255, 255, 0.87)' />
</IconButton>)}
<Notify open={this.state.openNotifyMenu} anchorEl={this.state.anchorEl} onRequestClose={this.handleCloseNotify}/>
{/* User avatar*/}
<UserAvatar
<UserAvatarComponent
onTouchTap={this.handleAvatarTouchTap}
fullName={this.props.fullName}
fileName={this.props.avatar}
fullName={this.props.fullName!}
fileName={this.props.avatar!}
size={32}
style={styles.avatarStyle}
style={this.styles.avatarStyle}
/>
<Popover
open={this.state.openAvatarMenu}
@@ -244,8 +237,8 @@ export class HomeHeader extends Component {
onRequestClose={this.handleRequestClose}
>
<Menu>
<MenuItem style={{ backgroundColor: 'white', color: blue500, fontSize: '14px' }} primaryText="MY ACCOUNT" />
<MenuItem primaryText="LOGOUT" style={{ fontSize: '14px' }} onClick={this.handleLogout.bind(this)} />
<MenuItem style={{ backgroundColor: 'white', color: blue500, fontSize: '14px' }} primaryText='MY ACCOUNT' />
<MenuItem primaryText='LOGOUT' style={{ fontSize: '14px' }} onClick={this.handleLogout.bind(this)} />
</Menu>
</Popover>
@@ -254,26 +247,24 @@ export class HomeHeader extends Component {
</Toolbar>
)
}
}
// - Map dispatch to props
const mapDispatchToProps = (dispatch, ownProps) => {
const mapDispatchToProps = (dispatch: Function, ownProps: IHomeHeaderComponentProps) => {
return {
logout: () => dispatch(authorizeActions.dbLogout())
}
}
// - Map state to props
const mapStateToProps = (state, ownProps) => {
let notifyCount = state.notify.userNotifies
const mapStateToProps = (state: any, ownProps: IHomeHeaderComponentProps) => {
let notifyCount = state.notify.userNotifies
? Object
.keys(state.notify.userNotifies)
.filter((key)=> !state.notify.userNotifies[key].isSeen).length
.filter((key) => !state.notify.userNotifies[key].isSeen).length
: 0
return {
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : '',
@@ -284,4 +275,4 @@ const mapStateToProps = (state, ownProps) => {
}
// - 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 SvgImage from 'material-ui/svg-icons/image/image'
// - Import app components
// - Import API
// - Import actions
import * as imageGalleryActions from 'imageGalleryActions'
import * as imageGalleryActions from 'actions/imageGalleryActions'
import { IImgComponentProps } from './IImgComponentProps'
import { IImgComponentState } from './IImgComponentState'
/**
* Create component class
*/
export class Img extends Component {
export class ImgComponent extends Component<IImgComponentProps,IImgComponentState> {
static propTypes = {
/**
* Use for getting url address from server
*/
fileName: PropTypes.string,
/**
* Avatar style
*/
style: PropTypes.object
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'
}
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
constructor (props: IImgComponentProps) {
super(props)
//Defaul state
// Defaul state
this.state = {
isImageLoaded: false
}
@@ -49,7 +60,7 @@ export class Img extends Component {
/**
* Will be called on loading image
*
*
* @memberof Img
*/
handleLoadImage = () => {
@@ -62,38 +73,16 @@ export class Img extends Component {
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
const styles = {
loding: {
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
width: '100%',
height: '100px',
position: 'relative',
color: '#cacecd',
fontWeight: 100
},
loadingContent: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
},
loadingImage: {
fill: 'aliceblue',
width: '50px',
height: '50px'
}
}
render () {
let { fileName, style } = this.props
let { isImageLoaded } = this.state
return (
<div>
<img onLoad={this.handleLoadImage} src={fileName || ''} style={isImageLoaded ? style : { display: 'none' }} />
<div style={{ backgroundColor: 'blue' }} style={isImageLoaded ? { display: 'none' } : styles.loding}>
<div style={styles.loadingContent}>
<SvgImage style={styles.loadingImage} />
<div style={Object.assign({},{ backgroundColor: 'white' }, isImageLoaded ? { display: 'none' } : this.styles.loding) }>
<div style={this.styles.loadingContent as any}>
<SvgImage style={this.styles.loadingImage} />
<div>Image has not loaded</div>
</div>
</div>
@@ -102,17 +91,15 @@ export class Img extends Component {
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
const mapDispatchToProps = (dispatch: any, ownProps: IImgComponentProps) => {
return {
}
}
@@ -122,7 +109,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
const mapStateToProps = (state: any, ownProps: IImgComponentProps) => {
return {
avatarURL: state.imageGallery.imageURLList,
imageRequests: state.imageGallery.imageRequests
@@ -130,4 +117,4 @@ const mapStateToProps = (state, ownProps) => {
}
// - 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 API
// - Import actions
import * as imageGalleryActions from 'imageGalleryActions'
import * as imageGalleryActions from 'actions/imageGalleryActions'
import { IImgCoverComponentProps } from './IImgCoverComponentProps'
import { IImgCoverComponentState } from './IImgCoverComponentState'
/**
* Create component class
*/
export class ImgCover extends Component {
export class ImgCoverComponent extends Component<IImgCoverComponentProps,IImgCoverComponentState> {
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
* @param {object} props is an object properties of component
*/
constructor(props) {
constructor (props: IImgCoverComponentProps) {
super(props)
//Defaul state
// Defaul state
this.state = {
isImageLoaded: false
}
@@ -65,8 +93,8 @@ export class ImgCover extends Component {
/**
* Will be called on loading image
*
* @memberof Img
*
* @memberof ImgCoverComponent
*/
handleLoadImage = () => {
this.setState({
@@ -78,54 +106,24 @@ export class ImgCover extends Component {
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
render () {
let { fileName, style } = this.props
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 (
<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}
</div>
<div style={{ backgroundColor: 'blue' }} style={isImageLoaded ? { display: 'none' } : styles.loding}>
<div style={styles.loadingContent}>
<SvgImage style={styles.loadingImage} />
<div style={Object.assign({},{ backgroundColor: 'blue' },isImageLoaded ? { display: 'none' } : this.styles.loding)}>
<div style={this.styles.loadingContent as any}>
<SvgImage style={this.styles.loadingImage} />
<div>Image has not loaded</div>
</div>
</div>
@@ -135,15 +133,13 @@ export class ImgCover extends Component {
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
const mapDispatchToProps = (dispatch: any, ownProps: IImgCoverComponentProps) => {
return {
}
}
@@ -154,7 +150,7 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
const mapStateToProps = (state: any, ownProps: IImgCoverComponentProps) => {
return {
avatarURL: state.imageGallery.imageURLList,
imageRequests: state.imageGallery.imageRequests
@@ -163,4 +159,4 @@ const mapStateToProps = (state, ownProps) => {
}
// - 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 actions
import * as authorizeActions from 'authorizeActions'
import * as authorizeActions from 'actions/authorizeActions'
import { ILoginComponentProps } from './ILoginComponentProps'
import { ILoginComponentState } from './ILoginComponentState'
// - Create Login component class
export class Login extends Component {
export class LoginComponent extends Component<ILoginComponentProps,ILoginComponentState> {
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props) {
constructor (props: ILoginComponentProps) {
super(props)
this.state = {
@@ -27,8 +28,7 @@ export class Login extends Component {
emailInputError: '',
passwordInput: '',
passwordInputError: '',
confirmInputError: ''
}
// Binding function to `this`
this.handleForm = this.handleForm.bind(this)
@@ -39,15 +39,14 @@ export class Login extends Component {
* Handle data on input change
* @param {event} evt is an event of inputs of element on change
*/
handleInputChange = (evt) => {
const target = evt.target
handleInputChange = (event: any) => {
const target = event.target
const value = target.type === 'checkbox' ? target.checked : target.value
const name = target.name
this.setState({
[name]: value
})
switch (name) {
case 'emailInput':
this.setState({
@@ -88,7 +87,7 @@ export class Login extends Component {
}
if (!error) {
this.props.login(
this.props.login!(
this.state.emailInput,
this.state.passwordInput
)
@@ -96,48 +95,46 @@ export class Login extends Component {
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
render () {
const paperStyle = {
minHeight: 370,
width: 450,
margin: 20,
textAlign: 'center',
display: 'block',
margin: "auto"
margin: 'auto'
}
return (
<form>
<h1 style={{
textAlign: "center",
padding: "20px",
fontSize: "30px",
textAlign: 'center',
padding: '20px',
fontSize: '30px',
fontWeight: 500,
lineHeight: "32px",
margin: "auto",
color: "rgba(138, 148, 138, 0.2)"
lineHeight: '32px',
margin: 'auto',
color: 'rgba(138, 148, 138, 0.2)'
}}>Green</h1>
<div className="animate-bottom">
<div className='animate-bottom'>
<Paper style={paperStyle} zDepth={1} rounded={false} >
<div style={{ padding: "48px 40px 36px" }}>
<div style={{ padding: '48px 40px 36px' }}>
<div style={{
paddingLeft: "40px",
paddingRight: "40px"
paddingLeft: '40px',
paddingRight: '40px'
}}>
<h2 style={{
textAlign: "left",
paddingTop: "16px",
fontSize: "24px",
textAlign: 'left',
paddingTop: '16px',
fontSize: '24px',
fontWeight: 400,
lineHeight: "32px",
lineHeight: '32px',
margin: 0
}}>Sign in</h2>
</div>
@@ -145,29 +142,29 @@ export class Login extends Component {
<TextField
onChange={this.handleInputChange}
errorText={this.state.emailInputError}
name="emailInput"
floatingLabelStyle={{ fontSize: "15px" }}
floatingLabelText="Email"
type="email"
name='emailInput'
floatingLabelStyle={{ fontSize: '15px' }}
floatingLabelText='Email'
type='email'
tabIndex={1}
/><br />
<TextField
onChange={this.handleInputChange}
errorText={this.state.passwordInputError}
name="passwordInput"
floatingLabelStyle={{ fontSize: "15px" }}
floatingLabelText="Password"
type="password"
name='passwordInput'
floatingLabelStyle={{ fontSize: '15px' }}
floatingLabelText='Password'
type='password'
tabIndex={2}
/><br />
<br />
<br />
<div className="login__button-box">
<div className='login__button-box'>
<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 >
<RaisedButton label="Login" primary={true} onClick={this.handleForm} tabIndex={3} />
<RaisedButton label='Login' primary={true} onClick={this.handleForm} tabIndex={3} />
</div>
</div>
@@ -185,13 +182,13 @@ export class Login extends Component {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch, ownProps) => {
const mapDispatchToProps = (dispatch: any, ownProps: ILoginComponentProps) => {
return {
login: (username, password) => {
dispatch(authorizeActions.dbLogin(username, password))
login: (email: string, password: string) => {
dispatch(authorizeActions.dbLogin(email, password))
},
signupPage: () => {
dispatch(push("/signup"))
dispatch(push('/signup'))
}
}
}
@@ -202,11 +199,11 @@ const mapDispatchToProps = (dispatch, ownProps) => {
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state, ownProps) => {
const mapStateToProps = (state: any, ownProps: ILoginComponentProps) => {
return {
}
}
// - 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
*

View File

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

View File

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