[New Feature] Add firestore to data layer

This commit is contained in:
Qolzam
2017-11-30 13:41:02 +07:00
parent 92379b615c
commit 22a5b46f36
67 changed files with 1844 additions and 615 deletions

View File

@@ -3,6 +3,7 @@ import moment from 'moment'
// - Import domain
import { Comment } from 'core/domain/comments'
import { Post } from 'core/domain/posts'
import { SocialError } from 'core/domain/common'
// - Import action types
@@ -11,6 +12,7 @@ import { CommentActionType } from 'constants/commentActionType'
// - Import actions
import * as globalActions from 'actions/globalActions'
import * as notifyActions from 'actions/notifyActions'
import * as postActions from 'actions/postActions'
import { IServiceProvider, ServiceProvide } from 'core/factories'
import { ICommentService } from 'core/services/comments'
@@ -43,7 +45,7 @@ export const dbAddComment = (ownerPostUserId: string | null,newComment: Comment,
text: newComment.text
}
return commentService.addComment(newComment.postId,comment)
return commentService.addComment(comment)
.then((commentKey: string) => {
dispatch(addComment({id: commentKey! ,...comment}))
callBack()
@@ -70,12 +72,42 @@ export const dbAddComment = (ownerPostUserId: string | null,newComment: Comment,
/**
* Get all comments from database
*/
export const dbGetComments = () => {
export const dbGetComments = (ownerUserId: string, postId: string) => {
return (dispatch: any, getState: Function) => {
let uid: string = getState().authorize.uid
if (uid) {
return commentService.getComments((comments: {[postId: string]: {[commentId: string]: Comment}}) => {
return commentService.getComments(postId, (comments: {[postId: string]: {[commentId: string]: Comment}}) => {
/**
* Workout getting the number of post's comment and getting three last comments
*/
dispatch(addCommentList(comments))
let commentsCount: number
const state = getState()
const post: Post = state.post.userPosts[ownerUserId][postId]
if (!post) {
return
}
if (comments && Object.keys(comments).length > 0) {
commentsCount = Object.keys(comments).length
let sortedObjects = comments as any
// Sort posts with creation date
sortedObjects.sort((a: any, b: any) => {
return parseInt(b.creationDate, 10) - parseInt(a.creationDate, 10)
})
if (!post.comments) {
post.comments = {}
}
Object.keys(sortedObjects).slice(0, 3).forEach((commentId) => {
post.comments![commentId] = {
id: commentId,
...sortedObjects[commentId]
}
})
dispatch(postActions.updatePost(post.ownerUserId!,post))
}
})
}
}
@@ -107,7 +139,7 @@ export const dbUpdateComment = (id: string, postId: string, text: string) => {
userId: uid
}
return commentService.updateComment(id,postId,updatedComment)
return commentService.updateComment(updatedComment)
.then(() => {
dispatch(updateComment( id, postId, text))
dispatch(globalActions.hideTopLoading())
@@ -118,7 +150,6 @@ export const dbUpdateComment = (id: string, postId: string, text: string) => {
})
}
}
/**

View File

@@ -30,7 +30,7 @@ export const defaultDataEnable = () => {
/**
* Default data loaded status will be false
* @param {boolean} status
* @param {boolean} status
*/
export const defaultDataDisable = () => {
return{
@@ -56,6 +56,7 @@ export const showNotificationSuccess = () => {
* Hide global message
*/
export const hideMessage = () => {
hideTopLoading()
return{
type: GlobalActionType.HIDE_MESSAGE_GLOBAL
}

View File

@@ -3,6 +3,7 @@ import { Action } from 'redux'
// - Import domain
import { Post } from 'core/domain/posts'
import { Comment } from 'core/domain/comments'
import { SocialError } from 'core/domain/common'
// - Import utility components
@@ -27,7 +28,7 @@ const postService: IPostService = serviceProvider.createPostService()
* @param {any} newPost
* @param {Function} callBack
*/
export let dbAddPost = (newPost: any, callBack: Function) => {
export let dbAddPost = (newPost: Post, callBack: Function) => {
return (dispatch: any, getState: Function) => {
let uid: string = getState().authorize.uid
@@ -39,11 +40,13 @@ export let dbAddPost = (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,
comments: {},
votes: {},
image: '',
imageFullPath: '',
video: '',
@@ -52,14 +55,14 @@ export let dbAddPost = (newPost: any, callBack: Function) => {
deleted: false
}
return postService.addPost(uid,post).then((postKey: string) => {
return postService.addPost(post).then((postKey: string) => {
dispatch(addPost(uid, {
...post,
id: postKey
}))
callBack()
})
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
}
}
@@ -95,7 +98,7 @@ export const dbAddImagePost = (newPost: Post, callBack: Function) => {
deleted: false
}
return postService.addPost(uid,post).then((postKey: string) => {
return postService.addPost(post).then((postKey: string) => {
dispatch(addPost(uid, {
...post,
id: postKey
@@ -104,7 +107,7 @@ export const dbAddImagePost = (newPost: Post, callBack: Function) => {
dispatch(globalActions.hideTopLoading())
})
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
}
@@ -115,51 +118,25 @@ export const dbAddImagePost = (newPost: Post, 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: Post, callBack: Function) => {
console.log(newPost)
export const dbUpdatePost = (updatedPost: Post, callBack: Function) => {
return (dispatch: any, getState: Function) => {
dispatch(globalActions.showTopLoading())
// Get current user id
let uid: string = getState().authorize.uid
// Write the new data simultaneously in the list
let updates: any = {}
let post: Post = getState().post.userPosts[uid][newPost.id!]
let updatedPost: Post = {
postTypeId: post.postTypeId,
creationDate: post.creationDate,
deleteDate: 0,
score: post.score,
viewCount: post.viewCount,
body: newPost.body ? newPost.body : post.body || '',
ownerUserId: uid,
ownerDisplayName: post.ownerDisplayName,
ownerAvatar: post.ownerAvatar,
lastEditDate: moment().unix(),
tags: newPost.tags ? newPost.tags : (post.tags || []),
commentCounter: post.commentCounter,
image: newPost.image ? newPost.image : post.image,
imageFullPath: newPost.imageFullPath!,
video: '',
disableComments: newPost.disableComments !== undefined ? newPost.disableComments : (post.disableComments ? post.disableComments : false),
disableSharing: newPost.disableSharing !== undefined ? newPost.disableSharing : (post.disableSharing ? post.disableSharing : false),
deleted: false
}
return postService.updatePost(updatedPost).then(() => {
return postService.updatePost(uid,newPost.id,updatedPost).then(() => {
dispatch(updatePost(uid, { id: newPost.id, ...updatedPost }))
dispatch(updatePost(uid, { ...updatedPost }))
callBack()
dispatch(globalActions.hideTopLoading())
})
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
dispatch(globalActions.hideTopLoading())
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
dispatch(globalActions.hideTopLoading())
})
})
}
}
@@ -176,15 +153,15 @@ export const dbDeletePost = (id: string) => {
// Get current user id
let uid: string = getState().authorize.uid
return postService.deletePost(uid,id).then(() => {
return postService.deletePost(id).then(() => {
dispatch(deletePost(uid, id))
dispatch(globalActions.hideTopLoading())
})
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
dispatch(globalActions.hideTopLoading())
})
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
dispatch(globalActions.hideTopLoading())
})
}
}
@@ -200,9 +177,9 @@ export const dbGetPosts = () => {
return postService.getPosts(uid).then((posts: { [postId: string]: Post }) => {
dispatch(addPosts(uid, posts))
})
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
})
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
})
}
}
@@ -217,12 +194,12 @@ export const dbGetPostById = (uid: string, postId: string) => {
return (dispatch: any, getState: Function) => {
if (uid) {
return postService.getPostById(uid,postId).then((post: Post) => {
return postService.getPostById(postId).then((post: Post) => {
dispatch(addPost(uid, post))
})
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
})
.catch((error: SocialError) => {
dispatch(globalActions.showErrorMessage(error.message))
})
}
}
@@ -233,7 +210,7 @@ export const dbGetPostById = (uid: string, postId: string) => {
* @param uid posts owner identifier
*/
export const dbGetPostsByUserId = (uid: string) => {
return (dispatch: any, getState: Function) => {
return (dispatch: Function, getState: Function) => {
if (uid) {
return postService.getPosts(uid).then((posts: { [postId: string]: Post }) => {

View File

@@ -1,5 +1,5 @@
// - Import react components
import _ from 'lodash'
// - Import domain
import { Profile } from 'core/domain/users'
import { SocialError } from 'core/domain/common'
@@ -26,14 +26,14 @@ export const dbGetUserInfo = () => {
return (dispatch: any, getState: Function) => {
let uid: string = getState().authorize.uid
if (uid) {
return userService.getUserProfile(uid).then((userProfile: Profile) => {
dispatch(addUserInfo(uid, {
avatar: userProfile.avatar,
email: userProfile.email,
fullName: userProfile.fullName,
banner: userProfile.banner,
tagLine: userProfile.tagLine
tagLine: userProfile.tagLine,
creationDate: userProfile.creationDate
}))
})
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
@@ -63,7 +63,8 @@ export const dbGetUserInfoByUserId = (uid: string, callerKey: string) => {
email: userProfile.email,
fullName: userProfile.fullName,
banner: userProfile.banner,
tagLine: userProfile.tagLine
tagLine: userProfile.tagLine,
creationDate: userProfile.creationDate
}))
switch (callerKey) {
@@ -97,7 +98,8 @@ export const dbUpdateUserInfo = (newProfile: Profile) => {
banner: newProfile.banner || profile.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',
email: newProfile.email || profile.email || '',
fullName: newProfile.fullName || profile.fullName || '',
tagLine: newProfile.tagLine || profile.tagLine || ''
tagLine: newProfile.tagLine || profile.tagLine || '',
creationDate: newProfile.creationDate
}
return userService.updateUserProfile(uid,updatedProfie).then(() => {
@@ -112,11 +114,13 @@ export const dbUpdateUserInfo = (newProfile: Profile) => {
}
// - Get people info from database
export const dbGetPeopleInfo = () => {
export const dbGetPeopleInfo = (page?: number) => {
return (dispatch: any, getState: Function) => {
let uid: string = getState().authorize.uid
const {authorize, user} = getState()
let uid: string = authorize.uid
if (uid) {
return userService.getUsersProfile(uid)
const lastKey = ''
return userService.getUsersProfile(uid, lastKey)
.then((usersProfile: {[userId: string]: Profile}) => {
dispatch(addPeopleInfo(usersProfile))
})

View File

@@ -9,9 +9,11 @@ import { Vote } from 'core/domain/votes'
// - Import actions
import * as globalActions from 'actions/globalActions'
import * as notifyActions from 'actions/notifyActions'
import * as postActions from 'actions/postActions'
import { IServiceProvider, ServiceProvide } from 'core/factories'
import { IVoteService } from 'core/services/votes'
import { Post } from 'core/domain/posts'
const serviceProvider: IServiceProvider = new ServiceProvide()
const voteService: IVoteService = serviceProvider.createVoteService()
@@ -26,7 +28,8 @@ const voteService: IVoteService = serviceProvider.createVoteService()
export const dbAddVote = (postId: string,ownerPostUserId: string) => {
return (dispatch: any, getState: Function) => {
let uid: string = getState().authorize.uid
const state = getState()
let uid: string = state.authorize.uid
let vote: Vote = {
postId: postId,
creationDate: moment().unix(),
@@ -34,41 +37,52 @@ export const dbAddVote = (postId: string,ownerPostUserId: string) => {
userAvatar: getState().user.info[uid].avatar,
userId: uid
}
const post: Post = state.post.userPosts[ownerPostUserId][postId]
post.score! += 1
dispatch(postActions.updatePost(ownerPostUserId,post))
return voteService.addVote(vote).then((voteKey: string) => {
dispatch(addVote(
{
...vote,
id: voteKey
}))
if (uid !== ownerPostUserId) {
dispatch(notifyActions.dbAddNotification(
{
description: 'Vote on your post.',
url: `/${ownerPostUserId}/posts/${postId}`,
notifyRecieverUserId: ownerPostUserId,notifierUserId:uid,
notifyRecieverUserId: ownerPostUserId,notifierUserId: uid,
isSeen: false
}))
}
})
.catch((error) => dispatch(globalActions.showErrorMessage(error.message)))
.catch((error) => {
post.score! -= 1
dispatch(postActions.updatePost(ownerPostUserId,post))
dispatch(globalActions.showErrorMessage(error.message))
})
}
}
/**
* Get all votes from database
*/
export const dbGetVotes = () => {
export const dbGetVotes = (userId: string, postId: string) => {
return (dispatch: any, getState: Function) => {
let uid: string = getState().authorize.uid
if (uid) {
return voteService
.getVotes()
.getVotes(postId)
.then((postVotes: { [postId: string]: { [voteId: string]: Vote } }) => {
dispatch(addVoteList(postVotes))
const state = getState()
const post: Post = state.post.userPosts[userId][postId]
if (!post) {
return
}
const votes = postVotes[postId]
if (votes && Object.keys(votes).length > 0) {
post.score = Object.keys(votes).length
}
})
}
@@ -80,19 +94,24 @@ export const dbGetVotes = () => {
* @param {string} id of vote
* @param {string} postId is the identifier of the post which vote belong to
*/
export const dbDeleteVote = (postId: string) => {
export const dbDeleteVote = (postId: string, ownerPostUserId: string) => {
return (dispatch: any, getState: Function) => {
const state = getState()
// Get current user id
let uid: string = getState().authorize.uid
let uid: string = state.authorize.uid
let votes: {[voteId: string]: Vote} = getState().vote.postVotes[postId]
let id: string = Object.keys(votes).filter((key) => votes[key].userId === uid)[0]
return voteService.deleteVote(id,postId).then(() => {
dispatch(deleteVote(id, postId))
const vote = votes[id]
const post: Post = state.post.userPosts[ownerPostUserId][postId]
post.score! -= 1
dispatch(postActions.updatePost(ownerPostUserId,post))
return voteService.deleteVote(vote).then(x => x)
.catch((error: any) => {
post.score! += 1
dispatch(postActions.updatePost(ownerPostUserId,post))
dispatch(globalActions.showErrorMessage(error.message))
})
.catch((error: any) => dispatch(globalActions.showErrorMessage(error.message)))
}
}
@@ -117,7 +136,7 @@ export const deleteVote = (id: string, postId: string) => {
/**
* Ad a list of vote
* @param {[postId:string]: {[voteId: string]: Vote}} votes a list of vote
* @param {[postId:string]: {[voteId: string]: Vote}} votes a list of vote
*/
export const addVoteList = (votes: {[postId: string]: {[voteId: string]: Vote}}) => {
return { type: VoteActionType.ADD_VOTE_LIST, payload: votes }

View File

@@ -1,5 +1,3 @@
// - Import react component
import { storageRef } from 'data/firebaseClient'
// - Interface declaration
interface FileReaderEventTarget extends EventTarget {
@@ -27,41 +25,6 @@ const convertImageToCanvas = (image: HTMLImageElement | HTMLCanvasElement | HTML
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
@@ -141,7 +104,6 @@ export default {
dataURLToBlob,
convertImageToCanvas,
getExtension,
constraintImage,
uploadImage
constraintImage
}

View File

@@ -7,7 +7,8 @@ import FlatButton from 'material-ui/FlatButton'
import TextField from 'material-ui/TextField'
import Divider from 'material-ui/Divider'
import { ListItem } from 'material-ui/List'
import { grey400, darkBlack, lightBlack } from 'material-ui/styles/colors'
import { grey400, darkBlack, lightBlack, tealA400 } from 'material-ui/styles/colors'
import LinearProgress from 'material-ui/LinearProgress'
// - Import actions
import * as commentActions from 'actions/commentActions'
@@ -65,6 +66,10 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
},
writeCommentTextField: {
width: '100%'
},
progressbar: {
height: '1.5px',
backgroundColor: 'rgb(245, 243, 243)'
}
}
@@ -133,7 +138,7 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
* @return {DOM} list of comments' DOM
*/
commentList = () => {
let comments = this.props.comments
let comments = this.props.commentSlides
if (comments) {
let parsedComments: Comment[] = []
@@ -178,11 +183,8 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
</div>}
secondaryTextLines={2}
/>
)
})
}
}
@@ -191,9 +193,41 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
* @return {react element} return the DOM which rendered by component
*/
render () {
const {comments} = this.props
/**
* Comment list box
*/
const commentWriteBox = (<div>
<Divider />
<Paper zDepth={0} className='animate2-top10' style={{ position: 'relative', overflowY: 'auto', padding: '12px 16px', display: (this.props.open ? 'block' : 'none') }}>
<div style={{ display: 'flex' }}>
<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...'
underlineShow={false}
multiLine={true}
rows={1}
hintStyle={{ fontWeight: 100, fontSize: '14px' }}
rowsMax={4}
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} />
</Paper>
</div>)
/**
* Return Elements
*/
return (
<div>
<div style={this.props.comments && Object.keys(this.props.comments).length > 0 ? { display: 'block' } : { display: 'none' }}>
<div style={this.props.commentSlides && Object.keys(this.props.commentSlides).length > 0 ? { display: 'block' } : { display: 'none' }}>
<Divider />
<Paper zDepth={0} className='animate-top' style={!this.props.open ? { display: 'block' } : { display: 'none' }}>
@@ -206,37 +240,22 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
</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' }}>
<CommentListComponent comments={this.props.comments} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/>
</Paper>) : ''}
{
!comments
? this.props.open ? <LinearProgress style={this.styles.progressbar} mode='indeterminate' color={tealA400} /> : ''
: (Object.keys(comments).length > 0
? (<Paper zDepth={0} style={this.props.open ? { display: 'block', padding: '0px 0px' } : { display: 'none', padding: '12px 16px' }}>
<CommentListComponent comments={comments} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/>
</Paper>)
: '')
}
</div>
{!this.props.disableComments ? (<div>
<Divider />
<Paper zDepth={0} className='animate2-top10' style={{ position: 'relative', overflowY: 'auto', padding: '12px 16px', display: (this.props.open ? 'block' : 'none') }}>
<div style={{ display: 'flex' }}>
<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...'
underlineShow={false}
multiLine={true}
rows={1}
hintStyle={{ fontWeight: 100, fontSize: '14px' }}
rowsMax={4}
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} />
</Paper>
</div>) : ''}
{
!this.props.disableComments
? commentWriteBox
: ''
}
</div>
)
}
@@ -266,11 +285,15 @@ const mapDispatchToProps = (dispatch: any, ownProps: ICommentGroupComponentProps
* @return {object} props of component
*/
const mapStateToProps = (state: any, ownProps: ICommentGroupComponentProps) => {
const {post, user, authorize} = state
const {ownerPostUserId, postId} = ownProps
const commentSlides = post.userPosts[ownerPostUserId] && post.userPosts[ownerPostUserId][postId] ? post.userPosts[ownerPostUserId][postId].comments : {}
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
commentSlides,
avatar: user.info && user.info[state.authorize.uid] ? user.info[authorize.uid].avatar || '' : '',
fullName: user.info && user.info[state.authorize.uid] ? user.info[authorize.uid].fullName || '' : '',
userInfo: user.info
}
}

View File

@@ -10,6 +10,14 @@ export interface ICommentGroupComponentProps {
*/
comments?: {[commentId: string]: Comment}
/**
* Commnets show on slide preview
*
* @type {{[commentId: string]: Comment}}
* @memberof ICommentGroupComponentProps
*/
commentSlides?: {[commentId: string]: Comment}
/**
* The post identifier which comment belong to
*

View File

@@ -3,6 +3,7 @@ import React, { Component } from 'react'
import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import Paper from 'material-ui/Paper'
import InfiniteScroll from 'react-infinite-scroller'
// - Import app components
import UserBoxList from 'components/userBoxList'
@@ -39,6 +40,12 @@ export class FindPeopleComponent extends Component<IFindPeopleComponentProps, IF
}
loadItems (page: number) {
console.log('------------------------')
console.log(page)
console.log('------------------------')
}
componentWillMount () {
this.props.loadPeople!()
}
@@ -63,9 +70,18 @@ export class FindPeopleComponent extends Component<IFindPeopleComponentProps, IF
right: 0
}
}
const loader = <div className='loader'>Loading ...</div>
return (
<div>
<InfiniteScroll
pageStart={0}
loadMore={this.loadItems.bind(this)}
hasMore={false}
loader={loader}>
<div className='tracks'>
{this.props.peopleInfo && Object.keys(this.props.peopleInfo).length !== 0 ? (<div>
<div className='profile__title'>
Suggestions for you
@@ -75,6 +91,8 @@ export class FindPeopleComponent extends Component<IFindPeopleComponentProps, IF
</div>) : (<div className='g__title-center'>
Nothing to show! :(
</div>)}
</div>
</InfiniteScroll>
</div>
)
}

View File

@@ -1,4 +1,5 @@
// - Import react components
import { HomeRouter } from 'routes'
import React, { Component } from 'react'
import _ from 'lodash'
import { Route, Switch, withRouter, Redirect, NavLink } from 'react-router-dom'
@@ -111,8 +112,7 @@ export class HomeComponent extends Component<IHomeComponentProps, IHomeComponent
goTo!('/login')
return
}
// if (!isVerifide) {
if (false) {
if (!isVerifide) {
goTo!('/emailVerification')
} else if (!global.defaultLoadDataStatus) {
@@ -131,7 +131,7 @@ export class HomeComponent extends Component<IHomeComponentProps, IHomeComponent
* @memberof Home
*/
render () {
const {loaded} = this.props
const {loaded, authed, mergedPosts} = this.props
return (
<div id='home'>
<HomeHeader sidebar={this.state.sidebarOpen} sidebarStatus={this.state.sidebarStatus} />
@@ -153,35 +153,7 @@ export class HomeComponent extends Component<IHomeComponentProps, IHomeComponent
</SidebarContent>
<SidebarMain>
{loaded ? (<Switch>
<Route path='/people/:tab?' render={() => {
return (
this.props.authed
? <People />
: <Redirect to='/login' />
)
}} />
<Route path='/tag/:tag' render={({match}) => {
return (
this.props.authed
? <div className='blog'><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='/' render={() => {
return (
this.props.authed
? <div className='blog'><StreamComponent homeTitle='Home' posts={this.props.mergedPosts} displayWriting={true} /></div>
: <Redirect to='/login' />
)
}} />
</Switch>)
: ''}
<HomeRouter enabled={loaded!} data={{mergedPosts}} />
</SidebarMain>
</Sidebar>
@@ -196,11 +168,9 @@ const mapDispatchToProps = (dispatch: any, ownProps: IHomeComponentProps) => {
return {
loadData: () => {
dispatch(commentActions.dbGetComments())
dispatch(imageGalleryActions.dbGetImageGallery())
dispatch(postActions.dbGetPosts())
dispatch(userActions.dbGetUserInfo())
dispatch(voteActions.dbGetVotes())
dispatch(notifyActions.dbGetNotifications())
dispatch(circleActions.dbGetCircles())
@@ -209,8 +179,6 @@ const mapDispatchToProps = (dispatch: any, ownProps: IHomeComponentProps) => {
dispatch(imageGalleryActions.clearAllData())
dispatch(postActions.clearAllData())
dispatch(userActions.clearAllData())
dispatch(commentActions.clearAllData())
dispatch(voteActions.clearAllvotes())
dispatch(notifyActions.clearAllNotifications())
dispatch(circleActions.clearAllCircles())
dispatch(globalActions.clearTemp())
@@ -234,7 +202,7 @@ const mapDispatchToProps = (dispatch: any, ownProps: IHomeComponentProps) => {
* @return {object} props of component
*/
const mapStateToProps = (state: any, ownProps: IHomeComponentProps) => {
const { authorize, global, user, post, comment, imageGallery, vote, notify, circle } = state
const { authorize, global, user, post, imageGallery, notify, circle } = state
const { uid } = authorize
let mergedPosts = {}
const circles = circle ? (circle.userCircles[uid] || {}) : {}
@@ -251,7 +219,7 @@ const mapStateToProps = (state: any, ownProps: IHomeComponentProps) => {
mainStyle: global.sidebarMainStyle,
mergedPosts,
global,
loaded: user.loaded && post.loaded && comment.loaded && imageGallery.loaded && vote.loaded && notify.loaded && circle.loaded
loaded: user.loaded && post.loaded && imageGallery.loaded && notify.loaded && circle.loaded
}
}

View File

@@ -13,7 +13,7 @@ export interface ILoginComponentProps {
*
* @memberof ILoginComponentProps
*/
loginWithOAuth: (type: OAuthType) => any
loginWithOAuth?: (type: OAuthType) => any
/**
* Redirect to signup page

View File

@@ -8,13 +8,9 @@ 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 EmailVerification from 'components/emailVerification'
import Login from 'components/login'
import ResetPassword from 'components/resetPassword'
import Setting from 'components/setting'
import MasterLoading from 'components/masterLoading'
import MasterRouter from 'routes/MasterRouter'
import { IMasterComponentProps } from './IMasterComponentProps'
import { IMasterComponentState } from './IMasterComponentState'
import { ServiceProvide, IServiceProvider } from 'core/factories'
@@ -80,7 +76,7 @@ export class MasterComponent extends Component<IMasterComponentProps, IMasterCom
console.log('====================================')
}
componentWillMount () {
componentDidMount () {
this._authourizeService.onAuthStateChanged((isVerifide: boolean, user: any) => {
const {global, clearData, loadDataGuest, defaultDataDisable, defaultDataEnable, login, logout } = this.props
@@ -114,9 +110,9 @@ export class MasterComponent extends Component<IMasterComponentProps, IMasterCom
*
* @memberof Master
*/
public render (): React.ReactElement<{}> {
public render () {
const { progress, global, loaded, guest } = this.props
const { progress, global, loaded, guest, uid } = this.props
const { loading, isVerifide } = this.state
return (
@@ -129,25 +125,7 @@ export class MasterComponent extends Component<IMasterComponentProps, IMasterCom
<div className='title'>Loading ... </div>
</div>
<MasterLoading activeLoading={loading} handleLoading={this.handleLoading} />
{(!loading) ? (<Switch>
<Route path='/signup' component={Signup} />
<Route path='/emailVerification' component={EmailVerification} />
<Route path='/settings' component={Setting} />
<Route path='/resetPassword' component={ResetPassword} />
<Route path='/login' render={() => {
return (
this.props.authed
? <Redirect to='/' />
: <Login />
)
}
} />
<Route render={() => <Home uid={this.props.uid} />} />
</Switch>)
: ''
}
<MasterRouter enabled={!loading} data={{uid}} />
<Snackbar
open={this.props.global.messageOpen}
message={this.props.global.message}

View File

@@ -1,74 +1,23 @@
import { Comment } from 'core/domain/comments'
import { Post } from 'core/domain/posts/post'
export interface IPostComponentProps {
/**
* The context of a post
*/
body: string
/**
* The number of comment on a post
*/
commentCounter: number
/**
* Creation post date
*/
creationDate: number
/**
* Post identifier
*/
id: string
/**
* Post image address
*/
image: string
/**
* The last time date when post has was edited
*/
lastEditDate: number
/**
* The name of the user who created the post
*/
ownerDisplayName: string
/**
* The identifier of the user who created the post
*/
ownerUserId: string
/**
* The avatar address of the user who created the post
* //TODO: User avatar should be as an attribute and [avatar] should be deleted
*/
ownerAvatar: string
/**
* The avatar address of the user who created the post
* Post object
*
* @type {Post}
* @memberof IPostComponentProps
*/
avatar?: string
/**
* If post is only [0]text, [1]whith picture, ...
*/
postTypeId: string
/**
* The number votes on a post
*/
score: number
/**
* Array of tags on a post
*/
tags: string[]
/**
* The video address of a post
*/
video: string
/**
* If it's true comment will be disabled on a post
*/
disableComments: boolean
/**
* If it's true sharing will be disabled on a post
*/
disableSharing: boolean
/**
* The number of users who has visited the post
*/
viewCount: boolean
post: Post
/**
* Owner's post avatar
*
* @type {string}
* @memberof IPostComponentProps
*/
avatar: string
/**
* User full name
@@ -78,14 +27,6 @@ export interface IPostComponentProps {
*/
fullName?: string
/**
* Number of comments on the post
*
* @type {number}
* @memberof IPostComponentProps
*/
commentCount?: number
/**
* Number of vote on a post
*
@@ -100,7 +41,7 @@ export interface IPostComponentProps {
* @type {boolean}
* @memberof IPostComponentProps
*/
userVoteStatus?: boolean
currentUserVote?: boolean
/**
* Current user is the owner of the post {true} or not {false}
@@ -158,4 +99,19 @@ export interface IPostComponentProps {
* @memberof IPostComponentProps
*/
setHomeTitle?: (title: string) => any
/**
* Get the comments of a post
*
* @memberof IPostComponentProps
*/
getPostComments: (ownerUserId: string, postId: string) => any
/**
* Commnets
*
* @type {{[commentId: string]: Comment}}
* @memberof ICommentGroupComponentProps
*/
commentList?: {[commentId: string]: Comment}
}

View File

@@ -40,6 +40,7 @@ import UserAvatar from 'components/userAvatar'
// - Import actions
import * as voteActions from 'actions/voteActions'
import * as postActions from 'actions/postActions'
import * as commentActions from 'actions/commentActions'
import * as globalActions from 'actions/globalActions'
import { IPostComponentProps } from './IPostComponentProps'
import { IPostComponentState } from './IPostComponentState'
@@ -47,80 +48,6 @@ import { IPostComponentState } from './IPostComponentState'
// - Create component class
export class PostComponent extends Component<IPostComponentProps,IPostComponentState> {
static propTypes = {
/**
* The context of a post
*/
body: PropTypes.string,
/**
* The number of comment on a post
*/
commentCounter: PropTypes.number,
/**
* Creation post date
*/
creationDate: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
/**
* Post identifier
*/
id: PropTypes.string,
/**
* Post image address
*/
image: PropTypes.string,
/**
* The last time date when post has was edited
*/
lastEditDate: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
/**
* The name of the user who created the post
*/
ownerDisplayName: PropTypes.string,
/**
* The identifier of the user who created the post
*/
ownerUserId: PropTypes.string,
/**
* The avatar address of the user who created the post
*/
ownerAvatar: PropTypes.string,
/**
* If post is only [0]text, [1]whith picture, ...
*/
postTypeId: PropTypes.number,
/**
* The number votes on a post
*/
score: PropTypes.number,
/**
* Array of tags on a post
*/
tags: PropTypes.array,
/**
* The video address of a post
*/
video: PropTypes.string,
/**
* If it's true comment will be disabled on a post
*/
disableComments: PropTypes.bool,
/**
* If it's true sharing will be disabled on a post
*/
disableSharing: PropTypes.bool,
/**
* The number of users who has visited the post
*/
viewCount: PropTypes.number
}
styles = {
counter: {
lineHeight: '36px',
@@ -155,11 +82,12 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
*/
constructor (props: IPostComponentProps) {
super(props)
const {post} = props
this.state = {
/**
* Post text
*/
text: this.props.body,
text: post.body!,
/**
* It's true if whole the text post is visible
*/
@@ -175,11 +103,11 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
/**
* If it's true comment will be disabled on post
*/
disableComments: this.props.disableComments,
disableComments: post.disableComments!,
/**
* If it's true share will be disabled on post
*/
disableSharing: this.props.disableSharing,
disableSharing: post.disableSharing!,
/**
* Title of share post
*/
@@ -212,6 +140,11 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
* @param {event} evt passed by clicking on comment slide show
*/
handleOpenComments = () => {
const { getPostComments, commentList, post} = this.props
const {id, ownerUserId} = post
if (!commentList) {
getPostComments(ownerUserId!, id!)
}
this.setState({
openComments: !this.state.openComments
})
@@ -248,7 +181,8 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
* @memberof Post
*/
handleDelete = () => {
this.props.delete!(this.props.id)
const {post} = this.props
this.props.delete!(post.id!)
}
/**
@@ -297,7 +231,7 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
* @memberof Post
*/
handleVote = () => {
if (this.props.userVoteStatus) {
if (this.props.currentUserVote) {
this.props.unvote!()
} else {
this.props.vote!()
@@ -330,23 +264,24 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
* @return {react element} return the DOM which rendered by component
*/
render () {
const {post ,setHomeTitle, goTo, fullName, isPostOwner, commentList, avatar} = this.props
const RightIconMenu = () => (
<IconMenu iconButtonElement={IconButtonElement} style={{ display: 'block', position: 'absolute', top: '0px', right: '4px' }}>
<MenuItem primaryText='Edit' onClick={this.handleOpenPostWrite} />
<MenuItem primaryText='Delete' onClick={this.handleDelete} />
<MenuItem primaryText={this.props.disableComments ? 'Enable comments' : 'Disable comments'} onClick={() => this.props.toggleDisableComments!(!this.props.disableComments)} />
<MenuItem primaryText={this.props.disableSharing ? 'Enable sharing' : 'Disable sharing'} onClick={() => this.props.toggleSharingComments!(!this.props.disableSharing)} />
<MenuItem primaryText={post.disableComments ? 'Enable comments' : 'Disable comments'} onClick={() => this.props.toggleDisableComments!(!post.disableComments)} />
<MenuItem primaryText={post.disableSharing ? 'Enable sharing' : 'Disable sharing'} onClick={() => this.props.toggleSharingComments!(!post.disableSharing)} />
</IconMenu>
)
const {ownerUserId,setHomeTitle, goTo, ownerDisplayName,creationDate, avatar, fullName, isPostOwner,image, body} = this.props
const {ownerUserId, ownerDisplayName, creationDate, image, body} = post
// Define variables
return (
<Card>
<CardHeader
title={<NavLink to={`/${ownerUserId}`}>{ownerDisplayName}</NavLink>}
subtitle={moment.unix(creationDate).fromNow() + ' | public'}
subtitle={moment.unix(creationDate!).fromNow() + ' | public'}
avatar={<NavLink to={`/${ownerUserId}`}><UserAvatar fullName={fullName!} fileName={avatar!} size={36} /></NavLink>}
>
{isPostOwner ? ( <div style={this.styles.rightIconMenu as any}><RightIconMenu /></div>) : ''}
@@ -384,25 +319,25 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
<Checkbox
checkedIcon={<SvgFavorite style={{fill: '#4CAF50'}}/>}
uncheckedIcon={<SvgFavoriteBorder style={{fill: '#757575'}} />}
defaultChecked={this.props.userVoteStatus}
checked={this.props.currentUserVote}
style={{transform: 'translate(6px, 6px)'}}
/>
</div>
<div style={this.styles.counter}> {this.props.voteCount! > 0 ? this.props.voteCount : ''} </div>
</div>
<div style={{ display: 'flex' }}>
{!this.props.disableComments ? (<div style={{display: 'inherit'}}><FloatingActionButton onClick={this.handleOpenComments} style={{ margin: '0 8px' }} backgroundColor={grey200} iconStyle={{ color: grey600, fill: grey600, height: '36px', width: '36px' }} zDepth={1} secondary={false}>
{!post.disableComments ? (<div style={{display: 'inherit'}}><FloatingActionButton onClick={this.handleOpenComments} style={{ margin: '0 8px' }} backgroundColor={grey200} iconStyle={{ color: grey600, fill: grey600, height: '36px', width: '36px' }} zDepth={1} secondary={false}>
<SvgComment viewBox='0 -9 24 34' style={{ height: '30px', width: '30px' }} /> 3
</FloatingActionButton>
<div style={this.styles.counter}>{this.props.commentCount! > 0 ? this.props.commentCount : ''} </div></div>) : ''}
{!this.props.disableSharing ? (<FloatingActionButton onClick={this.handleOpenShare} style={{ margin: '0 8px' }} backgroundColor={grey200} iconStyle={{ color: grey600, fill: grey600, height: '36px', width: '36px' }} zDepth={1} secondary={false}>
<div style={this.styles.counter}>{post.commentCounter! > 0 ? post.commentCounter : ''} </div></div>) : ''}
{!post.disableSharing ? (<FloatingActionButton onClick={this.handleOpenShare} style={{ margin: '0 8px' }} backgroundColor={grey200} iconStyle={{ color: grey600, fill: grey600, height: '36px', width: '36px' }} zDepth={1} secondary={false}>
<SvgShare viewBox='0 -9 24 34' style={{ height: '30px', width: '30px' }} />
</FloatingActionButton>) : ''}
</div>
</div>
</CardActions>
<CommentGroup open={this.state.openComments} ownerPostUserId={this.props.ownerUserId} onToggleRequest={this.handleOpenComments} isPostOwner={this.props.isPostOwner!} disableComments={this.props.disableComments} postId={this.props.id} />
<CommentGroup open={this.state.openComments} comments={commentList} ownerPostUserId={post.ownerUserId!} onToggleRequest={this.handleOpenComments} isPostOwner={this.props.isPostOwner!} disableComments={post.disableComments!} postId={post.id!} />
{/* Copy link dialog*/}
<Dialog
@@ -421,7 +356,7 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
<MenuItem primaryText='Copy Link' leftIcon={<SvgLink />} onClick={this.handleCopyLink} />
</Menu>
</Paper>)
: <TextField fullWidth={true} id='text-field-default' defaultValue={`${location.origin}/${this.props.ownerUserId}/posts/${this.props.id}`} />
: <TextField fullWidth={true} id='text-field-default' defaultValue={`${location.origin}/${post.ownerUserId}/posts/${post.id}`} />
}
</Dialog>
@@ -429,11 +364,7 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
open={this.state.openPostWrite}
onRequestClose={this.handleClosePostWrite}
edit={true}
text= {this.props.body}
image= {this.props.image ? this.props.image : ''}
id= {this.props.id}
disableComments= {this.props.disableComments}
disableSharing= {this.props.disableSharing}
postModel= {post}
/>
</Card>
@@ -449,14 +380,22 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch: any, ownProps: IPostComponentProps) => {
const {post} = ownProps
return {
vote: () => dispatch(voteActions.dbAddVote(ownProps.id,ownProps.ownerUserId)),
unvote: () => dispatch(voteActions.dbDeleteVote(ownProps.id)) ,
vote: () => dispatch(voteActions.dbAddVote(post.id!,post.ownerUserId!)),
unvote: () => dispatch(voteActions.dbDeleteVote(post.id!, post.ownerUserId!)) ,
delete: (id: string) => dispatch(postActions.dbDeletePost(id)),
toggleDisableComments: (status: boolean) => dispatch(postActions.dbUpdatePost({id: ownProps.id, disableComments: status}, (x: any) => x)),
toggleSharingComments: (status: boolean) => dispatch(postActions.dbUpdatePost({id: ownProps.id, disableSharing: status},(x: any) => x)),
toggleDisableComments: (status: boolean) => {
post.disableComments = status
dispatch(postActions.dbUpdatePost(post, (x: any) => x))
},
toggleSharingComments: (status: boolean) => {
post.disableSharing = status
dispatch(postActions.dbUpdatePost({id: post.id!, disableSharing: status},(x: any) => x))
},
goTo: (url: string) => dispatch(push(url)),
setHomeTitle: (title: string) => dispatch(globalActions.setHeaderTitle(title || ''))
setHomeTitle: (title: string) => dispatch(globalActions.setHeaderTitle(title || '')),
getPostComments: (ownerUserId: string, postId: string) => dispatch(commentActions.dbGetComments(ownerUserId,postId))
}
}
@@ -468,17 +407,20 @@ const mapDispatchToProps = (dispatch: any, ownProps: IPostComponentProps) => {
* @return {object} props of component
*/
const mapStateToProps = (state: any, ownProps: IPostComponentProps) => {
const {uid} = state.authorize
let votes = state.vote.postVotes[ownProps.id]
const post = (state.post.userPosts[uid] ? Object.keys(state.post.userPosts[uid]).filter((key) => { return ownProps.id === key }).length : 0)
const {post, vote, authorize, comment} = state
const {uid} = authorize
let currentUserVote = post.votes ? post.votes[uid] : false
const postModel = post.userPosts[ownProps.post.ownerUserId!][ownProps.post.id!]
const postOwner = (post.userPosts[uid] ? Object.keys(post.userPosts[uid]).filter((key) => { return ownProps.post.id === key }).length : 0)
const commentList: { [commentId: string]: Comment } = comment.postComments[ownProps.post.id!]
return {
avatar: state.user.info && state.user.info[ownProps.ownerUserId] ? state.user.info[ownProps.ownerUserId].avatar || '' : '',
fullName: state.user.info && state.user.info[ownProps.ownerUserId] ? state.user.info[ownProps.ownerUserId].fullName || '' : '',
commentCount: state.comment.postComments[ownProps.id] ? Object.keys(state.comment.postComments[ownProps.id]).length : 0,
voteCount: state.vote.postVotes[ownProps.id] ? Object.keys(state.vote.postVotes[ownProps.id]).length : 0,
userVoteStatus: votes && Object.keys(votes).filter((key) => votes[key].userId === state.authorize.uid)[0] ? true : false,
isPostOwner: post > 0
commentList,
avatar: state.user.info && state.user.info[ownProps.post.ownerUserId!] ? state.user.info[ownProps.post.ownerUserId!].avatar || '' : '',
fullName: state.user.info && state.user.info[ownProps.post.ownerUserId!] ? state.user.info[ownProps.post.ownerUserId!].fullName || '' : '',
voteCount: postModel.score,
currentUserVote,
isPostOwner: postOwner > 0
}
}

View File

@@ -45,39 +45,17 @@ export interface IPostWriteComponentProps {
* @type {string}
* @memberof IPostWriteComponentProps
*/
avatar?: string
ownerAvatar?: string
/**
* User name
*
* @type {string}
* @memberof IPostWriteComponentProps
* The post owner name
*/
name?: string
ownerDisplayName: string
/**
* Post image full path
*
* @type {string}
* @memberof IPostWriteComponentProps
* Post model
*/
imageFullPath?: string
/**
* Comment on the post is disabled {true} or not {false}
*
* @type {boolean}
* @memberof IPostWriteComponentProps
*/
disableComments?: boolean
/**
* Sharing on a post is disabled {true} or not {false}
*
* @type {boolean}
* @memberof IPostWriteComponentProps
*/
disableSharing?: boolean
postModel: Post
/**
* Save a post

View File

@@ -34,37 +34,6 @@ import { Post } from 'core/domain/posts'
// - Create PostWrite component class
export class PostWriteComponent extends Component<IPostWriteComponentProps,IPostWriteComponentState> {
static propTypes = {
/**
* If it's true post writing page will be open
*/
open: PropTypes.bool,
/**
* Recieve request close function
*/
onRequestClose: PropTypes.func,
/**
* Post write style
*/
style: PropTypes.object,
/**
* If it's true, post will be in edit view
*/
edit: PropTypes.bool.isRequired,
/**
* The text of post in editing state
*/
text: PropTypes.string,
/**
* The image of post in editing state
*/
image: PropTypes.string,
/**
* If post state is editing this id sould be filled with post identifier
*/
id: PropTypes.string
}
/**
* Component constructor
* @param {object} props is an object properties of component
@@ -73,6 +42,8 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
super(props)
const {postModel} = props
// Default state
this.state = {
/**
@@ -86,7 +57,7 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
/**
* The path identifier of image on the server
*/
imageFullPath: this.props.edit ? this.props.imageFullPath! : '',
imageFullPath: this.props.edit ? (postModel.imageFullPath ? postModel.imageFullPath! : '' ) : '',
/**
* If it's true gallery will be open
*/
@@ -98,11 +69,11 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
/**
* If it's true comment will be disabled on post
*/
disableComments: this.props.edit ? this.props.disableComments! : false,
disableComments: this.props.edit ? postModel.disableComments! : false,
/**
* If it's true share will be disabled on post
*/
disableSharing: this.props.edit ? this.props.disableSharing! : false
disableSharing: this.props.edit ? postModel.disableSharing! : false
}
@@ -154,7 +125,7 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
this.setState({
image: '',
imageFullPath: '',
disabledPost: false
disabledPost: this.state.postText.trim() === ''
})
}
@@ -163,7 +134,6 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
* @param {event} evt passed by clicking on the post button
*/
handlePost = () => {
const {
image,
imageFullPath,
@@ -172,13 +142,21 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
postText } = this.state
const {
id,
avatar,
name,
edit,
onRequestClose,
post,
update } = this.props
id,
ownerAvatar,
ownerDisplayName,
edit,
onRequestClose,
post,
update,
postModel
} = this.props
if (image === '' && postText.trim() === '') {
this.setState({
disabledPost: false
})
return
}
let tags = PostAPI.getContentTags(postText!)
@@ -190,8 +168,8 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
tags: tags,
image: image,
imageFullPath: imageFullPath,
ownerAvatar: avatar,
ownerDisplayName: name,
ownerAvatar: ownerAvatar,
ownerDisplayName: ownerDisplayName,
disableComments: disableComments,
disableSharing: disableSharing,
postTypeId: 1,
@@ -202,8 +180,8 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
post!({
body: postText,
tags: tags,
ownerAvatar: avatar,
ownerDisplayName: name,
ownerAvatar: ownerAvatar,
ownerDisplayName: ownerDisplayName,
disableComments: disableComments,
disableSharing: disableSharing,
postTypeId: 0,
@@ -212,15 +190,14 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
}, onRequestClose)
}
} else { // In edit status we pass post to update functions
update!({
id: id,
body: postText,
tags: tags,
image: image,
imageFullPath: imageFullPath,
disableComments: disableComments,
disableSharing: disableSharing
}, onRequestClose)
postModel.body = postText
postModel.tags = tags
postModel.image = image
postModel.imageFullPath = imageFullPath
postModel.disableComments = disableComments
postModel.disableSharing = disableSharing
update!(postModel, onRequestClose)
}
}
@@ -276,6 +253,7 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
componentWillReceiveProps (nextProps: IPostWriteComponentProps) {
if (!nextProps.open) {
const {postModel} = this.props
this.setState({
/**
* Post text
@@ -296,11 +274,11 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
/**
* If it's true comment will be disabled on post
*/
disableComments: this.props.edit ? this.props.disableComments! : false,
disableComments: this.props.edit ? postModel.disableComments! : false,
/**
* If it's true share will be disabled on post
*/
disableSharing: this.props.edit ? this.props.disableSharing! : false
disableSharing: this.props.edit ? postModel.disableSharing! : false
})
}
@@ -328,7 +306,7 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
<MenuItem onClick={this.handleToggleSharing} style={{ fontSize: '14px' }}>{!this.state.disableSharing ? 'Disable sharing' : 'Enable sharing'}</MenuItem>
</IconMenu>
)
let postAvatar = <UserAvatarComponent fullName={this.props.name!} fileName={this.props.avatar!} style={{ top: '8px' }} size={40} />
let postAvatar = <UserAvatarComponent fullName={this.props.ownerDisplayName!} fileName={this.props.ownerAvatar!} style={{ top: '8px' }} size={40} />
let author = (
<div>
@@ -341,7 +319,7 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps,IPost
overflow: 'hidden',
paddingLeft: '50px',
lineHeight: '25px'
}}>{this.props.name}</span><span style={{
}}>{this.props.ownerDisplayName}</span><span style={{
fontWeight: 100,
fontSize: '10px'
}}> | Public</span>

View File

@@ -161,23 +161,7 @@ export class StreamComponent extends Component<IStreamComponentProps,IStreamComp
<div key={post.id}>
{index > 1 || (!postBack.divided && index > 0) ? <div style={{ height: '16px' }}></div> : ''}
<PostComponent
body={post.body}
commentCounter={post.commentCounter}
creationDate={post.creationDate}
id={post.id}
image={post.image}
lastEditDate={post.lastEditDate}
ownerDisplayName={post.ownerDisplayName}
ownerUserId={post.ownerUserId}
ownerAvatar={post.ownerAvatar}
postTypeId={post.postTypeId}
score={post.score}
tags={post.tags}
video={post.video}
disableComments={post.disableComments}
disableSharing={post.disableSharing}
viewCount={posts.viewCount} />
<PostComponent post={post} />
</div>
)

View File

@@ -24,11 +24,11 @@ export class UserAvatarComponent extends Component<IUserAvatarComponentProps,IUs
/**
* Use for getting url address from server
*/
fileName: PropTypes.string.isRequired,
fileName: PropTypes.string,
/**
* User full name
*/
fullName: PropTypes.string.isRequired,
fullName: PropTypes.string,
/**
* Avatar style
*/
@@ -70,7 +70,7 @@ export class UserAvatarComponent extends Component<IUserAvatarComponentProps,IUs
<div style={{display: 'inherit'}}>
{(fileName && fileName !== '' && fileName !== 'noImage' )
? ( <Avatar backgroundColor='#ffffff' src={fileName} size={size || 36} style={style} onTouchTap={onTouchTap} />)
: (<Avatar backgroundColor='#00bcd4' size={size || 36} style={style} onTouchTap={onTouchTap}>{fullName.slice(0, 1)}</Avatar>) }
: (<Avatar backgroundColor='#00bcd4' size={size || 36} style={style} onTouchTap={onTouchTap}>{fullName ? fullName.slice(0, 1) : ''}</Avatar>) }
</div>
)
}

View File

@@ -2,6 +2,14 @@ import { BaseDomain } from 'core/domain/common'
export class Notification extends BaseDomain {
/**
* Notification identifier
*
* @type {string}
* @memberof Notification
*/
public id?: string
/**
* Description of notification
*

View File

@@ -1,5 +1,5 @@
import { BaseDomain } from 'core/domain/common'
import { Comment } from 'core/domain/comments'
export class Post extends BaseDomain {
/**
@@ -42,6 +42,14 @@ export class Post extends BaseDomain {
*/
public score?: number
/**
* List of voter identifier
*
* @type {{[voterId: string]: boolean}}
* @memberof Post
*/
votes?: {[voterId: string]: boolean}
/**
* Post view count
*
@@ -50,6 +58,14 @@ export class Post extends BaseDomain {
*/
public viewCount?: number
/**
* Store three last comments to show in slide preview comment
*
* @type {{[commentId: string]: Comment}}
* @memberof Post
*/
comments?: {[commentId: string]: Comment}
/**
* The text of post
*
@@ -136,7 +152,7 @@ export class Post extends BaseDomain {
* @type {Boolean}
* @memberof Post
*/
public disableComments?: Boolean
public disableComments?: boolean
/**
* If sharing post is disabled {true} or not {false}
@@ -144,7 +160,7 @@ export class Post extends BaseDomain {
* @type {Boolean}
* @memberof Post
*/
public disableSharing?: Boolean
public disableSharing?: boolean
/**
* If the post is deleted {true} or not false
@@ -152,6 +168,6 @@ export class Post extends BaseDomain {
* @type {Boolean}
* @memberof Post
*/
public deleted?: Boolean
public deleted?: boolean
}

View File

@@ -6,7 +6,9 @@ export class Profile extends BaseDomain {
public fullName: string,
public banner: string,
public tagLine: string,
public email?: string | null) {
public creationDate: number,
public email?: string | null
) {
super()
}

View File

@@ -30,7 +30,7 @@ import {
UserService,
VoteService,
StorageService
} from 'data/firebaseClient/services'
} from 'data/firestoreClient/services'
//#endregion

View File

@@ -10,9 +10,9 @@ import { Comment } from 'core/domain/comments'
*/
export interface ICommentService {
addComment: (postId: string, comment: Comment) => Promise<string>
getComments: (callback: (resultComments: { [postId: string]: { [commentId: string]: Comment } }) => void) => void
updateComment: (commentId: string, postId: string, comment: Comment) => Promise<void>
addComment: (comment: Comment) => Promise<string>
getComments: (postId: string, callback: (resultComments: { [postId: string]: { [commentId: string]: Comment } }) => void) => void
updateComment: (comment: Comment) => Promise<void>
deleteComment: (commentId: string, postId: string) => Promise<void>
}

View File

@@ -8,9 +8,9 @@ import { Post } from 'core/domain/posts'
* @interface IPostService
*/
export interface IPostService {
addPost: (userId: string, post: Post) => Promise<string>
updatePost: (userId: string, postId: string, post: Post) => Promise<void>
deletePost: (userId: string,postId: string) => Promise<void>
addPost: (post: Post) => Promise<string>
updatePost: (post: Post) => Promise<void>
deletePost: (postId: string) => Promise<void>
getPosts: (userId: string) => Promise<{ [postId: string]: Post }>
getPostById: (userId: string, postId: string) => Promise<Post>
getPostById: (postId: string) => Promise<Post>
}

View File

@@ -9,5 +9,5 @@ import { User, Profile } from 'core/domain/users'
export interface IUserService {
getUserProfile: (userId: string) => Promise<Profile>
updateUserProfile: (userId: string, profile: Profile) => Promise<void>
getUsersProfile: (userId: string) => Promise<{[userId: string]: Profile}>
getUsersProfile: (userId: string, lastKey?: string, numberOfItems?: number) => Promise<{[userId: string]: Profile}>
}

View File

@@ -9,6 +9,6 @@ import { Vote } from 'core/domain/votes'
*/
export interface IVoteService {
addVote: (vote: Vote) => Promise<string>
getVotes: () => Promise<{[postId: string]: {[voteId: string]: Vote}}>
deleteVote: (voteId: string, postId: string) => Promise<void>
getVotes: (postId: string) => Promise<{[postId: string]: {[voteId: string]: Vote}}>
deleteVote: (vote: Vote) => Promise<void>
}

View File

@@ -1,7 +1,4 @@
declare const process: any
import firebase from 'firebase'
try {
let config = {
apiKey: process.env.API_KEY,
@@ -12,7 +9,6 @@ try {
messagingSenderId: process.env.MESSAGING_SENDER_ID
}
console.log(firebase)
firebase.initializeApp(config)
} catch (error) {
console.log('=========Firebase initializer==============')

View File

@@ -1,3 +1,6 @@
import moment from 'moment'
import { Profile } from 'core/domain/users'
// - Import react components
import { firebaseRef, firebaseAuth } from 'data/firebaseClient'
@@ -68,8 +71,8 @@ export class AuthorizeService implements IAuthorizeService {
firebaseAuth()
.createUserWithEmailAndPassword(user.email as string, user.password as string)
.then((signupResult) => {
const {uid, email, displayName, photoURL} = signupResult
this.storeUserInformation(uid,email,displayName,photoURL).then(resolve)
const {uid, email} = signupResult
this.storeUserInformation(uid,email,user.fullName,'').then(resolve)
})
.catch((error: any) => reject(new SocialError(error.code, error.message)))
})
@@ -107,7 +110,7 @@ export class AuthorizeService implements IAuthorizeService {
firebaseAuth().onAuthStateChanged( (user: any) => {
let isVerifide = false
if (user) {
if (user.emailVerified) {
if (user.emailVerified || user.providerData[0].providerId.trim() !== 'password') {
isVerifide = true
} else {
isVerifide = false
@@ -210,15 +213,17 @@ export class AuthorizeService implements IAuthorizeService {
* @private
* @memberof AuthorizeService
*/
private storeUserInformation = (userId: string, email: string, fullName: string, avatar?: string) => {
private storeUserInformation = (userId: string, email: string, fullName: string, avatar: string) => {
return new Promise<RegisterUserResult>((resolve,reject) => {
firebaseRef.child(`users/${userId}/info`)
.set({
userId,
.set(new Profile(
avatar,
email,
fullName
})
fullName,
'',
'',
moment().unix(),
email
))
.then((result) => {
resolve(new RegisterUserResult(userId))
})

View File

@@ -1,5 +1,5 @@
// - Import react components
import { firebaseRef, firebaseAuth } from 'data/firebaseClient'
import { firebaseRef, firebaseAuth, db } from 'data/firebaseClient'
import { SocialError } from 'core/domain/common'
import { ICircleService } from 'core/services/circles'
@@ -18,13 +18,11 @@ export class CircleService implements ICircleService {
public addCircle: (userId: string, circle: Circle)
=> Promise<string> = (userId, circle) => {
return new Promise<string>((resolve,reject) => {
let circleRef = firebaseRef.child(`users/${userId}/circles`).push(circle)
circleRef.then(() => {
resolve(circleRef.key as string)
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})

View File

@@ -1,5 +1,6 @@
// - Import react components
import { firebaseRef, firebaseAuth } from 'data/firebaseClient'
import moment from 'moment'
import { SocialError } from 'core/domain/common'
import { Profile, UserProvider } from 'core/domain/users'
@@ -13,6 +14,7 @@ import { IUserService } from 'core/services/users'
* @implements {IUserService}
*/
export class UserService implements IUserService {
public getUserProfile: (userId: string)
=> Promise<Profile> = (userId) => {
return new Promise<Profile>((resolve, reject) => {
@@ -23,7 +25,7 @@ export class UserService implements IUserService {
if (Object.keys(userProfile).length === 0 && userProfile.constructor === Object) {
this.getUserProviderData(userId).then((providerData: UserProvider) => {
const {avatar,fullName, email} = providerData
const userProfile = new Profile(avatar,fullName,'','',email)
const userProfile = new Profile(avatar,fullName,'','', moment().unix(),email)
resolve(userProfile)
this.updateUserProfile(userId,userProfile)
})
@@ -52,10 +54,16 @@ export class UserService implements IUserService {
})
})
}
public getUsersProfile: (userId: string)
=> Promise<{ [userId: string]: Profile }> = (userId) => {
public getUsersProfile: (userId: string, page?: number, lastKey?: string)
=> Promise<{ [userId: string]: Profile }> = (userId, page, lastKey) => {
return new Promise<{ [userId: string]: Profile }>((resolve, reject) => {
let usersProfileRef: any = firebaseRef.child(`users`)
let usersProfileRef: any
if (page) {
const numberOfItems = (page * 12) + 12
usersProfileRef = firebaseRef.child(`users`).orderByKey().startAt(lastKey!).limitToFirst(numberOfItems)
} else {
usersProfileRef = firebaseRef.child(`users`).orderByKey()
}
usersProfileRef.once('value').then((snapshot: any) => {
let usersProfile: any = snapshot.val() || {}

View File

@@ -0,0 +1,31 @@
import firebase from 'firebase'
import 'firebase/firestore'
try {
let config = {
apiKey: process.env.API_KEY,
authDomain: process.env.AUTH_DOMAIN,
databaseURL: process.env.DATABASE_URL,
projectId: process.env.PROJECT_ID,
storageBucket: process.env.STORAGE_BUCKET,
messagingSenderId: process.env.MESSAGING_SENDER_ID
}
firebase.initializeApp(config)
} catch (error) {
console.log('=========Firebase firestore initializer==============')
console.log(error)
console.log('====================================')
}
// - Storage reference
export let storageRef = firebase.storage().ref()
// Initialize Cloud Firestore through Firebase
export const db = firebase.firestore()
// - Database authorize
export let firebaseAuth = firebase.auth
export let firebaseRef = firebase.database().ref()
// - Firebase default
export default firebase

View File

@@ -0,0 +1,264 @@
import { Profile } from 'core/domain/users'
// - Import react components
import { firebaseRef, firebaseAuth, db } from 'data/firestoreClient'
import { IAuthorizeService } from 'core/services/authorize'
import { User, UserProvider } from 'core/domain/users'
import { LoginUser, RegisterUserResult } from 'core/domain/authorize'
import { SocialError } from 'core/domain/common'
import { OAuthType } from 'core/domain/authorize/oauthType'
import moment from 'moment'
/**
* Firbase authorize service
*
* @export
* @class AuthorizeService
* @implements {IAuthorizeService}
*/
export class AuthorizeService implements IAuthorizeService {
/**
* Login the user
*
* @returns {Promise<LoginUser>}
* @memberof IAuthorizeService
*/
public login: (email: string, password: string) => Promise<LoginUser> = (email, password) => {
return new Promise<LoginUser>((resolve, reject) => {
firebaseAuth()
.signInWithEmailAndPassword(email, password)
.then((result) => {
resolve(new LoginUser(result.uid, result.emailVerified))
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
/**
* Logs out the user
*
* @returns {Promise<void>}
* @memberof IAuthorizeService
*/
public logout: () => Promise<void> = () => {
return new Promise<void>((resolve, reject) => {
firebaseAuth()
.signOut()
.then((result) => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
/**
* Register a user
*
* @returns {Promise<void>}
* @memberof IAuthorizeService
*/
public registerUser: (user: User) => Promise<RegisterUserResult> = (user) => {
return new Promise<RegisterUserResult>((resolve, reject) => {
firebaseAuth()
.createUserWithEmailAndPassword(user.email as string, user.password as string)
.then((signupResult) => {
const {uid, email} = signupResult
this.storeUserInformation(uid,email,user.fullName,'').then(resolve)
})
.catch((error: any) => reject(new SocialError(error.code, error.message)))
})
}
/**
* Update user password
*
* @returns {Promise<void>}
* @memberof IAuthorizeService
*/
public updatePassword: (newPassword: string) => Promise<void> = (newPassword) => {
return new Promise<void>((resolve, reject) => {
let user = firebaseAuth().currentUser
if (user) {
user.updatePassword(newPassword).then(() => {
// Update successful.
resolve()
}).catch((error: any) => {
// An error happened.
reject(new SocialError(error.code, error.message))
})
}
})
}
/**
* On user authorization changed event
*
* @memberof IAuthorizeService
*/
public onAuthStateChanged: (callBack: (isVerifide: boolean, user: User) => void) => any = (callBack) => {
firebaseAuth().onAuthStateChanged( (user: any) => {
let isVerifide = false
if (user) {
if (user.emailVerified || user.providerData[0].providerId.trim() !== 'password') {
isVerifide = true
} else {
isVerifide = false
}
}
callBack(isVerifide,user)
})
}
/**
* Reset user password
*
* @memberof AuthorizeService
*/
public resetPassword: (email: string) => Promise<void> = (email) => {
return new Promise<void>((resolve,reject) => {
let auth = firebaseAuth()
auth.sendPasswordResetEmail(email).then(function () {
resolve()
}).catch((error: any) => {
// An error happened.
reject(new SocialError(error.code, error.message))
})
})
}
/**
* Send verfication email to user email
*
* @memberof AuthorizeService
*/
public sendEmailVerification: () => Promise<void> = () => {
return new Promise<void>((resolve,reject) => {
let auth = firebaseAuth()
const user = auth.currentUser
if (user) {
user.sendEmailVerification().then(() => {
resolve()
}).catch((error: any) => {
// An error happened.
reject(new SocialError(error.code, error.message))
})
} else {
reject(new SocialError('authorizeService/nullException', 'User was null!'))
}
})
}
public loginWithOAuth: (type: OAuthType) => Promise<LoginUser> = (type) => {
return new Promise<LoginUser>((resolve,reject) => {
let provider: any
switch (type) {
case OAuthType.GITHUB:
provider = new firebaseAuth.GithubAuthProvider()
break
case OAuthType.FACEBOOK:
provider = new firebaseAuth.FacebookAuthProvider()
break
case OAuthType.GOOGLE:
provider = new firebaseAuth.GoogleAuthProvider()
break
default:
throw new SocialError('authorizeService/loginWithOAuth','None of OAuth type is matched!')
}
firebaseAuth().signInWithPopup(provider).then((result) => {
// This gives you a GitHub Access Token. You can use it to access the GitHub API.
let token = result.credential.accessToken
// The signed-in user info.
const {user} = result
const {credential} = result
const {uid, displayName, email, photoURL} = user
const {accessToken, providerId} = credential
this.storeUserProviderData(uid,email,displayName,photoURL,providerId,accessToken)
// this.storeUserInformation(uid,email,displayName,photoURL).then(resolve)
resolve(new LoginUser(user.uid,true,providerId,displayName,email,photoURL))
}).catch(function (error: any) {
// Handle Errors here.
let errorCode = error.code
let errorMessage = error.message
// The email of the user's account used.
let email = error.email
// The firebase.auth.AuthCredential type that was used.
let credential = error.credential
})
})
}
/**
* Store user information
*
* @private
* @memberof AuthorizeService
*/
private storeUserInformation = (userId: string, email: string, fullName: string, avatar: string) => {
return new Promise<RegisterUserResult>((resolve,reject) => {
db.doc(`userInfo/${userId}`).set(
{
avatar,
fullName,
creationDate: moment().unix(),
email
}
)
.then(() => {
alert(userId)
resolve(new RegisterUserResult(userId))
})
.catch((error: any) => reject(new SocialError(error.name, 'firestore/storeUserInformation : ' + error.message)))
})
}
/**
* Store user provider information
*
* @private
* @memberof AuthorizeService
*/
private storeUserProviderData = (
userId: string,
email: string,
fullName: string,
avatar: string,
providerId: string,
accessToken: string
) => {
return new Promise<RegisterUserResult>((resolve,reject) => {
db.doc(`userProviderInfo/${userId}`)
.set(
{
userId,
email,
fullName,
avatar,
providerId,
accessToken
}
)
.then(() => {
resolve(new RegisterUserResult(userId))
})
.catch((error: any) => reject(new SocialError(error.name, error.message)))
})
}
}

View File

@@ -0,0 +1,5 @@
import { AuthorizeService } from './AuthorizeService'
export {
AuthorizeService
}

View File

@@ -0,0 +1,119 @@
// - Import react components
import { firebaseRef, firebaseAuth, db } from 'data/firestoreClient'
import { SocialError } from 'core/domain/common'
import { ICircleService } from 'core/services/circles'
import { Circle, UserFollower } from 'core/domain/circles'
import { User } from 'core/domain/users'
/**
* Firbase circle service
*
* @export
* @class CircleService
* @implements {ICircleService}
*/
export class CircleService implements ICircleService {
public addCircle: (userId: string, circle: Circle)
=> Promise<string> = (userId, circle) => {
return new Promise<string>((resolve,reject) => {
let circleRef = db.doc(`users/${userId}`).collection(`circles`).add(circle)
circleRef.then((result) => {
resolve(result.id as string)
})
})
}
public addFollowingUser: (userId: string, circleId: string, userCircle: User, userFollower: UserFollower, userFollowingId: string)
=> Promise<void> = (userId, circleId, userCircle, userFollower, userFollowingId) => {
return new Promise<void>((resolve,reject) => {
const batch = db.batch()
const followerRef = db.doc(`users/${userId}/circles/${circleId}/users/${userFollowingId}`)
const followingRef = db.doc(`users/${userFollowingId}/circles/-Followers/users/${userId}`)
batch.update(followerRef, userCircle)
batch.update(followingRef, userFollower)
batch.commit().then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public deleteFollowingUser: (userId: string, circleId: string, userFollowingId: string)
=> Promise<void> = (userId, circleId, userFollowingId) => {
return new Promise<void>((resolve,reject) => {
const batch = db.batch()
const followerRef = db.doc(`users/${userId}/circles/${circleId}/users/${userFollowingId}`)
const followingRef = db.doc(`users/${userFollowingId}/circles/-Followers/users/${userId}`)
batch.delete(followerRef)
batch.delete(followingRef)
batch.commit().then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public updateCircle: (userId: string, circleId: string, circle: Circle)
=> Promise<void> = (userId, circleId, circle) => {
return new Promise<void>((resolve,reject) => {
const batch = db.batch()
const circleRef = db.doc(`users/${userId}/circles/${circleId}`)
batch.update(circleRef,circle)
batch.commit().then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public deleteCircle: (userId: string, circleId: string)
=> Promise<void> = (userId, circleId) => {
return new Promise<void>((resolve,reject) => {
const batch = db.batch()
const circleRef = db.doc(`users/${userId}/circles/${circleId}`)
batch.delete(circleRef)
batch.commit().then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public getCircles: (userId: string) => Promise<{ [circleId: string]: Circle }> = (userId) => {
return new Promise<{ [circleId: string]: Circle }>((resolve,reject) => {
let circlesRef = db.doc(`users/${userId}`).collection(`circles`)
circlesRef.onSnapshot((snapshot) => {
let parsedData: { [circleId: string]: Circle } = {}
snapshot.forEach((result) => {
parsedData[result.id] = {
id: result.id,
...result.data() as Circle
}
})
resolve(parsedData)
})
})
}
}

View File

@@ -0,0 +1,5 @@
import { CircleService } from './CircleService'
export {
CircleService
}

View File

@@ -0,0 +1,133 @@
// - Import react components
import { firebaseRef, firebaseAuth, db } from 'data/firestoreClient'
import _ from 'lodash'
import { SocialError } from 'core/domain/common'
import { ICommentService } from 'core/services/comments'
import { Comment } from 'core/domain/comments'
/**
* Firbase comment service
*
* @export
* @class CommentService
* @implements {ICommentService}
*/
export class CommentService implements ICommentService {
public addComment: (comment: Comment)
=> Promise<string> = (comment) => {
return new Promise<string>((resolve,reject) => {
const postRef = db.doc(`posts/${comment.postId}`)
let commentRef = postRef.collection('comments')
commentRef.add(comment).then((result) => {
resolve(result.id)
/**
* Add comment counter and three comments' slide preview
*/
db.runTransaction((transaction) => {
return transaction.get(postRef).then((postDoc) => {
if (postDoc.exists) {
const commentCount = postDoc.data().commentCounter + 1
transaction.update(postRef, { commentCounter: commentCount })
let comments = postDoc.data()
if (!comments) {
comments = {}
}
if (commentCount < 4) {
transaction.update(postRef, { comments: { ...comments, [result.id]: comment } })
} else {
let sortedObjects = comments
// Sort posts with creation date
sortedObjects.sort((a: any, b: any) => {
return parseInt(b.creationDate,10) - parseInt(a.creationDate,10)
})
const lastCommentId = Object.keys(sortedObjects)[2]
comments[lastCommentId] = {... comment}
transaction.update(postRef, { comments: { ...comments} })
}
}
})
})
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
public getComments: (postId: string, callback: (resultComments: { [postId: string]: { [commentId: string]: Comment } }) => void)
=> void = (postId, callback) => {
let commentsRef = db.doc(`posts/${postId}`).collection(`comments`)
commentsRef.onSnapshot((snapshot) => {
let parsedData: {[postId: string]: {[commentId: string]: Comment}} = {[postId]: {}}
snapshot.forEach((result) => {
parsedData[postId][result.id] = {
id: result.id,
...result.data() as Comment
}
})
if (callback) {
callback(parsedData)
}
})
}
public updateComment: (comment: Comment)
=> Promise<void> = (comment) => {
return new Promise<void>((resolve,reject) => {
const batch = db.batch()
const commentRef = db.doc(`posts/${comment.postId}/comments/${comment.id}`)
batch.update(commentRef, comment)
batch.commit().then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
public deleteComment: (commentId: string, postId: string)
=> Promise<void> = (commentId, postId) => {
return new Promise<void>((resolve,reject) => {
const batch = db.batch()
const postRef = db.doc(`posts/${postId}`)
const commentRef = postRef.collection(`comments`).doc(commentId)
batch.delete(commentRef)
batch.commit().then(() => {
resolve()
/**
* Delete comment counter and comments' slide preview
*/
db.runTransaction((transaction) => {
return transaction.get(postRef).then((postDoc) => {
if (postDoc.exists) {
const commentCount = postDoc.data().commentCounter - 1
transaction.update(postRef, { commentCounter: commentCount })
if (commentCount > 3) {
let comments = postDoc.data().comments
if (!comments) {
comments = {}
}
let parsedComments = {}
Object.keys(postDoc.data().comments).map((id) => {
if (id !== commentId) {
_.merge(parsedComments, { [id]: { ...comments[id] } })
}
})
transaction.update(postRef, { comments: { ...parsedComments}})
}
}
})
})
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
}

View File

@@ -0,0 +1,5 @@
import { CommentService } from './CommentService'
export {
CommentService
}

View File

@@ -0,0 +1,16 @@
// - Import react components
import { firebaseRef, firebaseAuth } from 'data/firebaseClient'
import { SocialError } from 'core/domain/common'
import { ICommonService } from 'core/services/common'
/**
* Firbase common service
*
* @export
* @class CommonService
* @implements {ICommonService}
*/
export class CommonService implements ICommonService {
}

View File

@@ -0,0 +1,5 @@
import { CommonService } from './CommonService'
export {
CommonService
}

View File

@@ -0,0 +1,41 @@
import { storageRef } from 'data/firestoreClient'
import { IStorageService } from 'core/services/files'
import { FileResult } from 'models/files/fileResult'
export class StorageService implements IStorageService {
/**
* Upload image on the server
* @param {file} file
* @param {string} fileName
*/
public uploadFile = (file: any, fileName: string, progress: (percentage: number, status: boolean) => void) => {
return new Promise<FileResult>((resolve, reject) => {
// Create a storage refrence
let storegeFile = storageRef.child(`images/${fileName}`)
// Upload file
let task = storegeFile.put(file)
task.then((result) => {
resolve(new FileResult(result.downloadURL!,result.metadata.fullPath))
}).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)
})
})
}
}

View File

@@ -0,0 +1,5 @@
import { StorageService } from './StorageService'
export {
StorageService
}

View File

@@ -0,0 +1,111 @@
import { FileResult } from 'models/files/fileResult'
// - Import react components
import { firebaseRef, firebaseAuth, storageRef, db } from 'data/firestoreClient'
import { SocialError } from 'core/domain/common'
import { IImageGalleryService } from 'core/services/imageGallery'
import { Image } from 'core/domain/imageGallery'
import { IStorageService } from 'core/services/files'
import { IServiceProvider, ServiceProvide } from 'core/factories'
/**
* Firbase image gallery service
*
* @export
* @class ImageGalleryService
* @implements {IImageGalleryService}
*/
export class ImageGalleryService implements IImageGalleryService {
private readonly storageService: IStorageService
private readonly serviceProvider: IServiceProvider
constructor () {
this.serviceProvider = new ServiceProvide()
this.storageService = this.serviceProvider.createStorageService()
}
public getImageGallery: (userId: string)
=> Promise<Image[]> = (userId) => {
return new Promise<Image[]>((resolve,reject) => {
let imagesRef = db.doc(`users/${userId}`).collection(`images`)
imagesRef.get().then((snapshot) => {
let parsedData: Image[] = []
snapshot.forEach((result) => {
parsedData.push({
id: result.id,
...result.data() as Image
})
})
resolve(parsedData)
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public saveImage: (userId: string, image: Image)
=> Promise<string> = (userId, image) => {
return new Promise<string>((resolve,reject) => {
let imageRef = db.doc(`users/${userId}`).collection(`images`).add(image)
imageRef.then((result) => {
resolve(result.id!)
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public deleteImage: (userId: string, imageId: string)
=> Promise<void> = (userId, imageId) => {
return new Promise<void>((resolve,reject) => {
const batch = db.batch()
const imageRef = db.doc(`users/${userId}/images/${imageId}`)
batch.delete(imageRef)
batch.commit().then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public uploadImage: (image: any, imageName: string, progressCallback: (percentage: number, status: boolean) => void)
=> Promise<FileResult> = (image, imageName, progressCallback) => {
return new Promise<FileResult>((resolve,reject) => {
this.storageService.uploadFile(image,imageName,progressCallback)
.then((result: FileResult) => {
resolve(result)
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
public downloadImage: (fileName: string)
=> Promise<string> = (fileName) => {
return new Promise<string>((resolve,reject) => {
// Create a reference to the file we want to download
let starsRef: any = storageRef.child(`images/${fileName}`)
// Get the download URL
starsRef.getDownloadURL().then((url: string) => {
resolve(url)
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
}

View File

@@ -0,0 +1,5 @@
import { ImageGalleryService } from './ImageGalleryService'
export {
ImageGalleryService
}

View File

@@ -0,0 +1,24 @@
import { AuthorizeService } from './authorize'
import { CircleService } from './circles'
import { CommentService } from './comments'
import { CommonService } from './common'
import { ImageGalleryService } from './imageGallery'
import { NotificationService } from './notifications'
import { PostService } from './posts'
import { UserService } from './users'
import { VoteService } from './votes'
import { StorageService } from './files'
export {
AuthorizeService,
CircleService,
CommentService,
CommonService,
ImageGalleryService,
NotificationService,
PostService,
UserService,
VoteService,
StorageService
}

View File

@@ -0,0 +1,5 @@
import { NotificationService } from './NotificationService'
export {
NotificationService
}

View File

@@ -0,0 +1,74 @@
// - Import react components
import { firebaseRef, firebaseAuth, db } from 'data/firestoreClient'
import { SocialError } from 'core/domain/common'
import { Notification } from 'core/domain/notifications'
import { INotificationService } from 'core/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) => {
db.doc(`users/${notification.notifyRecieverUserId}`).collection(`notifications`)
.add(notification)
.then(() => {
resolve()
})
})
}
public getNotifications: (userId: string, callback: (resultNotifications: {[notifyId: string]: Notification}) => void)
=> void = (userId,callback) => {
let notificationsRef = db.doc(`users/${userId}`).collection('notifications')
notificationsRef.onSnapshot((snapshot) => {
let parsedData: { [notifyId: string]: Notification } = {}
snapshot.forEach((result) => {
parsedData[result.id] = {
id: result.id,
...result.data() as Notification
}
})
callback(parsedData)
})
}
public deleteNotification: (notificationId: string, userId: string)
=> Promise < void > = (notificationId, userId) => {
return new Promise<void>((resolve, reject) => {
const batch = db.batch()
const notificationRef = db.doc(`users/${userId}/notifications/${notificationId}`)
batch.delete(notificationRef)
batch.commit().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) => {
const batch = db.batch()
const notificationRef = db.doc(`users/${userId}/notifications/${notificationId}`)
batch.update(notificationRef,notification)
batch.commit().then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
}

View File

@@ -0,0 +1,105 @@
// - Import react components
import { firebaseRef, firebaseAuth, db } from 'data/firestoreClient'
import { SocialError } from 'core/domain/common'
import { Post } from 'core/domain/posts'
import { IPostService } from 'core/services/posts'
import { IServiceProvider } from 'core/factories'
import { ICommentService } from 'core/services/comments'
import { ServiceProvide } from 'core/factories/serviceProvide'
/**
* Firbase post service
*
* @export
* @class PostService
* @implements {IPostService}
*/
export class PostService implements IPostService {
public addPost: (post: Post)
=> Promise<string> = (post) => {
return new Promise<string>((resolve,reject) => {
let postRef = db.collection(`posts`).add(post)
postRef.then((result) => {
resolve(result.id)
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
public updatePost: (post: Post)
=> Promise<void> = (post) => {
return new Promise<void>((resolve,reject) => {
const batch = db.batch()
const notificationRef = db.doc(`posts/${post.id}`)
batch.update(notificationRef, post)
batch.commit().then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
public deletePost: (postId: string)
=> Promise<void> = (postId) => {
return new Promise<void>((resolve,reject) => {
const batch = db.batch()
const notificationRef = db.doc(`posts/${postId}`)
batch.delete(notificationRef)
batch.commit().then(() => {
resolve()
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
public getPosts: (userId: string)
=> Promise<{ [postId: string]: Post }> = (userId) => {
return new Promise<{ [postId: string]: Post }>((resolve,reject) => {
let postsRef = db.collection(`posts`).where('ownerUserId', '==', userId)
postsRef.get().then((snapshot) => {
let parsedData: { [postId: string]: Post } = {}
snapshot.forEach((result) => {
parsedData[result.id] = {
id: result.id,
...result.data() as Post
}
})
resolve(parsedData)
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
public getPostById: (postId: string)
=> Promise<Post> = (postId) => {
return new Promise<Post>((resolve,reject) => {
let postsRef = db.doc(`posts/${postId}`)
postsRef.get().then((snapshot) => {
let newPost = snapshot.data() || {}
let post: Post = {
id: postId,
...newPost
}
resolve(post)
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
}

View File

@@ -0,0 +1,5 @@
import { PostService } from './PostService'
export {
PostService
}

View File

@@ -0,0 +1,92 @@
// - Import react components
import { firebaseRef, firebaseAuth, db } from 'data/firestoreClient'
import firebase from 'firebase'
import moment from 'moment'
import { SocialError } from 'core/domain/common'
import { Profile, UserProvider } from 'core/domain/users'
import { IUserService } from 'core/services/users'
/**
* Firbase user service
*
* @export
* @class UserService
* @implements {IUserService}
*/
export class UserService implements IUserService {
public getUserProfile: (userId: string)
=> Promise<Profile> = (userId) => {
return new Promise<Profile>((resolve, reject) => {
let userProfileRef = db.doc(`userInfo/${userId}`)
userProfileRef.get().then((snapshot) => {
if (!snapshot.exists) {
this.getUserProviderData(userId).then((providerData: UserProvider) => {
const {avatar,fullName, email} = providerData
const userProfile = new Profile(avatar,fullName,'','',moment().unix(),email)
resolve(userProfile)
this.updateUserProfile(userId,userProfile)
})
} else {
resolve(snapshot.data() as Profile)
}
})
.catch((error: any) => reject(new SocialError(error.code, 'firestore/getUserProfile :' + error.message)))
})
}
public updateUserProfile: (userId: string, profile: Profile)
=> Promise<void> = (userId, profile) => {
return new Promise<void>((resolve, reject) => {
const batch = db.batch()
const profileRef = db.doc(`userInfo/${userId}`)
batch.set(profileRef,{...profile})
batch.commit().then(() => {
resolve()
})
.catch((error: any) => reject(new SocialError(error.code, 'firestore/updateUserProfile' + error.message)))
})
}
public getUsersProfile: (userId: string, lastKey?: string, numberOfItems?: number)
=> Promise<{ [userId: string]: Profile }> = (userId, lastKey, numberOfItems = 15) => {
return new Promise<{ [userId: string]: Profile }>((resolve, reject) => {
let usersProfileRef: firebase.firestore.Query
if (lastKey) {
usersProfileRef = db.collection(`userInfo`).orderBy('creationDate', 'desc').startAfter(lastKey!).limit(numberOfItems)
} else {
usersProfileRef = db.collection(`userInfo`).orderBy('creationDate', 'desc')
}
usersProfileRef.get().then((snapshot) => {
let parsedData: { [userId: string]: Profile } = {}
snapshot.forEach((result) => {
parsedData[result.id] = {
...result.data() as Profile
}
})
resolve(parsedData)
})
.catch((error: any) => {
reject(new SocialError(error.code, error.message))
})
})
}
private getUserProviderData = (userId: string) => {
return new Promise<UserProvider>((resolve,reject) => {
let userProviderRef = db.doc(`userProviderInfo/${userId}`)
userProviderRef.get().then((snapshot) => {
let userProvider: UserProvider = snapshot.data() as UserProvider || {}
resolve(userProvider)
})
.catch((error: any) => {
reject(new SocialError(error.code, 'firestore/getUserProviderData' + error.message))
})
})
}
}

View File

@@ -0,0 +1,5 @@
import { UserService } from './UserService'
export {
UserService
}

View File

@@ -0,0 +1,106 @@
// - Import react components
import { firebaseRef, firebaseAuth, db } from 'data/firestoreClient'
import { SocialError } from 'core/domain/common'
import { Vote } from 'core/domain/votes'
import { IVoteService } from 'core/services/votes'
/**
* Firbase vote service
*
* @export
* @class VoteService
* @implements {IVoteService}
*/
export class VoteService implements IVoteService {
public addVote: (vote: Vote)
=> Promise<string> = (vote) => {
return new Promise<string>((resolve,reject) => {
const postRef = db.doc(`posts/${vote.postId}`)
let voteRef = postRef.collection(`votes`)
.add(vote)
voteRef.then((result) => {
resolve(result.id)
/**
* Add score
*/
db.runTransaction((transaction) => {
return transaction.get(postRef).then((postDoc) => {
if (postDoc.exists) {
const post = postDoc.data()
let {votes, score} = post
if (!votes) {
votes = {}
}
if (!score) {
score = 0
}
const newScore = score + 1
votes[vote.userId] = true
transaction.update(postRef, { votes: { ...votes}, score: newScore })
}
})
})
})
.catch((error: any) => {
reject(new SocialError(error.code,error.message))
})
})
}
public getVotes: (postId: string)
=> Promise<{ [postId: string]: { [voteId: string]: Vote } }> = (postId) => {
return new Promise<{ [postId: string]: { [voteId: string]: Vote } }>((resolve,reject) => {
let votesRef = db.doc(`posts/${postId}`).collection(`votes`)
votesRef.onSnapshot((snapshot) => {
let parsedData: {[postId: string]: {[voteId: string]: Vote}} = {[postId]: {}}
snapshot.forEach((result) => {
parsedData[postId][result.id] = {
id: result.id,
...result.data() as Vote
}
})
resolve(parsedData)
})
})
}
public deleteVote: (vote: Vote)
=> Promise<void> = (vote) => {
return new Promise<void>((resolve,reject) => {
const batch = db.batch()
const postRef = db.doc(`posts/${vote.postId}`)
let voteRef = postRef.collection(`votes`).doc(vote.id!)
batch.delete(voteRef)
batch.commit().then(() => {
resolve()
/**
* Remove score
*/
db.runTransaction((transaction) => {
return transaction.get(postRef).then((postDoc) => {
if (postDoc.exists) {
const post = postDoc.data()
let {votes, score} = post
if (!votes) {
votes = {}
}
if (!score) {
score = 0
}
const newScore = score + 1
votes[vote.userId] = true
transaction.update(postRef, { votes: { ...votes}, score: newScore })
}
})
})
})
.catch((error) => {
reject(new SocialError(error.code,error.message))
})
})
}
}

View File

@@ -0,0 +1,5 @@
import { VoteService } from './VoteService'
export {
VoteService
}

View File

@@ -12,9 +12,6 @@ import { Provider } from 'react-redux'
import store, { history } from 'store/configureStore'
import { ConnectedRouter } from 'react-router-redux'
import 'babel-core/register'
import 'babel-polyfill'
// - Import app components
import Master from 'components/master'
// import { App } from 'components/AWS'
@@ -23,9 +20,10 @@ import Master from 'components/master'
// tslint:disable-next-line:no-empty
store.subscribe(() => { })
// Needed for onTouchTap
// http://stackoverflow.com/a/34015469/988941
injectTapEventPlugin()
try { injectTapEventPlugin() } catch (e) { }
// This replaces the textColor value on the palette
// and then update the keys for each component that depends on it.
@@ -37,17 +35,6 @@ const muiTheme = getMuiTheme({
// App css
import 'applicationStyles'
const supportsHistory = 'pushState' in window.history
// ReactDOM.render(
// <Provider store={store}>
// <ConnectedRouter history={history}>
// <MuiThemeProvider muiTheme={getMuiTheme(lightBaseTheme)}>
// <Master />
// </MuiThemeProvider>
// </ConnectedRouter>
// </Provider>,
// document.getElementById('app')
// )
const render = (Component: any) => {
ReactDOM.render(
<AppContainer warnings={false}>
@@ -68,5 +55,5 @@ render(Master)
// Webpack Hot Module Replacement API
if (module.hot) {
module.hot.accept('components/master', () => { render(Master) })
module.hot.accept()
}

View File

@@ -28,7 +28,7 @@ export class AuthorizeState {
* @type {Boolean}
* @memberof AuthorizeState
*/
isVerfide: Boolean = false
isVerifide: Boolean = false
/**
* If user password is updated {true} or not {false}

View File

@@ -61,10 +61,10 @@ export let commentReducer = (state: CommentState = new CommentState(), action: I
}
}
case CommentActionType.DELETE_COMMENT:
let parsedComments = {}
if (!state.postComments![payload.postId]) {
return state
}
let parsedComments = {}
Object.keys(state.postComments![payload.postId]).map((id) => {
if (id !== payload.id) {
_.merge(parsedComments, { [id]: { ...state.postComments![payload.postId][id] } })

40
src/routes/HomeRouter.tsx Normal file
View File

@@ -0,0 +1,40 @@
// - Import react components
import PrivateRoute from './PrivateRoute'
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Route, Switch, withRouter, Redirect, NavLink } from 'react-router-dom'
// - Import app components
import StreamComponent from 'components/stream'
import Profile from 'components/profile'
import PostPage from 'components/postPage'
import People from 'components/people'
import { IRouterProps } from './IRouterProps'
/**
* Home Router
*/
export class HomeRouter extends Component<IRouterProps, any> {
render () {
const { enabled, match, data } = this.props
return (
enabled ? (<Switch>
<PrivateRoute path='/people/:tab?' component={<People />} />
<PrivateRoute path='/tag/:tag' component={(
<div className='blog'><StreamComponent displayWriting={false} homeTitle={`#${match.params.tag}`} posts={data.mergedPosts} /></div>
)} />
<Route path='/:userId/posts/:postId/:tag?' component={PostPage} />
<Route path='/:userId' component={Profile} />
<PrivateRoute path='/' component={(
<div className='blog'><StreamComponent homeTitle='Home' posts={data.mergedPosts} displayWriting={true} /></div>
)} />
</Switch>)
: ''
)
}
}
export default withRouter(connect(null, null)(HomeRouter as any))

35
src/routes/IRoute.ts Normal file
View File

@@ -0,0 +1,35 @@
import { Component } from 'react'
/**
* Route interface
*
* @export
* @interface IRoute
*/
export interface IRoute {
/**
* React component that would be rendered in routing
*
* @type {Component}
* @memberof IRoute
*/
component: any
/**
* Route path
*
* @type {string}
* @memberof IRoute
*/
path: string
/**
* If user is authorized {true} or not {false}
*
* @type {boolean}
* @memberof IRoute
*/
authed?: boolean
}

View File

@@ -0,0 +1,26 @@
export interface IRouterProps {
/**
* Enable routing {true} or not {false}
*
* @type {boolean}
* @memberof IRouterProps
*/
enabled: boolean
/**
* Router data for the components in routing
*
* @type {*}
* @memberof IRouterProps
*/
data?: any
/**
* Routing match
*
* @type {*}
* @memberof IRouterProps
*/
match: any
}

View File

@@ -0,0 +1,39 @@
// - Import react components
import PublicRoute from './PublicRoute'
import PrivateRoute from './PrivateRoute'
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Route, Switch, withRouter, Redirect, NavLink } from 'react-router-dom'
// - Import app components
import Home from 'components/home'
import Signup from 'components/signup'
import EmailVerification from 'components/emailVerification'
import Login from 'components/login'
import ResetPassword from 'components/resetPassword'
import Setting from 'components/setting'
import { IRouterProps } from './IRouterProps'
/**
* Master router
*/
export class MasterRouter extends Component<IRouterProps, any> {
render () {
const { enabled, match, data } = this.props
return (
enabled ? (<Switch>
<Route path='/signup' component={Signup} />
<Route path='/emailVerification' component={EmailVerification} />
<Route path='/settings' component={Setting} />
<Route path='/resetPassword' component={ResetPassword} />
<PublicRoute path='/login' component={<Login />} />
<Route render={() => <Home uid={data.uid} />} />
</Switch>)
: ''
)
}
}
export default withRouter(connect(null, null)(MasterRouter as any))

View File

@@ -0,0 +1,29 @@
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Route, Redirect } from 'react-router-dom'
import { IRoute } from './IRoute'
export class PrivateRoute extends Component<IRoute, any> {
render () {
const {authed, path, component} = this.props
return (
<Route path={path} render={() => {
return (
authed
? (() => component)()
: <Redirect to='/login' />
)
}} />
)
}
}
const mapStateToProps = (state: any, nexProps: IRoute) => {
const { authorize } = state
return {
authed: authorize.authed
}
}
export default connect(mapStateToProps)(PrivateRoute as any)

View File

@@ -0,0 +1,29 @@
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { Route, Redirect } from 'react-router-dom'
import { IRoute } from './IRoute'
export class PublicRoute extends Component<IRoute, any> {
render () {
const {authed, path, component} = this.props
return (
<Route path={path} render={() => {
return (
authed
? <Redirect to='/' />
: (() => component)()
)
}} />
)
}
}
const mapStateToProps = (state: any, nexProps: IRoute) => {
const { authorize } = state
return {
authed: authorize.authed
}
}
export default connect(mapStateToProps)(PublicRoute as any)

7
src/routes/index.ts Normal file
View File

@@ -0,0 +1,7 @@
import MasterRouter from './MasterRouter'
import HomeRouter from './HomeRouter'
export {
MasterRouter,
HomeRouter
}