[New Feature] Add firestore to data layer
This commit is contained in:
@@ -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) => {
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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 }) => {
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface ILoginComponentProps {
|
||||
*
|
||||
* @memberof ILoginComponentProps
|
||||
*/
|
||||
loginWithOAuth: (type: OAuthType) => any
|
||||
loginWithOAuth?: (type: OAuthType) => any
|
||||
|
||||
/**
|
||||
* Redirect to signup page
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*
|
||||
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ import {
|
||||
UserService,
|
||||
VoteService,
|
||||
StorageService
|
||||
} from 'data/firebaseClient/services'
|
||||
} from 'data/firestoreClient/services'
|
||||
|
||||
//#endregion
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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}>
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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==============')
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
|
||||
@@ -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() || {}
|
||||
|
||||
31
src/data/firestoreClient/index.ts
Normal file
31
src/data/firestoreClient/index.ts
Normal 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
|
||||
264
src/data/firestoreClient/services/authorize/AuthorizeService.ts
Normal file
264
src/data/firestoreClient/services/authorize/AuthorizeService.ts
Normal 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)))
|
||||
})
|
||||
}
|
||||
}
|
||||
5
src/data/firestoreClient/services/authorize/index.ts
Normal file
5
src/data/firestoreClient/services/authorize/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { AuthorizeService } from './AuthorizeService'
|
||||
|
||||
export {
|
||||
AuthorizeService
|
||||
}
|
||||
119
src/data/firestoreClient/services/circles/CircleService.ts
Normal file
119
src/data/firestoreClient/services/circles/CircleService.ts
Normal 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)
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
5
src/data/firestoreClient/services/circles/index.ts
Normal file
5
src/data/firestoreClient/services/circles/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { CircleService } from './CircleService'
|
||||
|
||||
export {
|
||||
CircleService
|
||||
}
|
||||
133
src/data/firestoreClient/services/comments/CommentService.ts
Normal file
133
src/data/firestoreClient/services/comments/CommentService.ts
Normal 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))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
5
src/data/firestoreClient/services/comments/index.ts
Normal file
5
src/data/firestoreClient/services/comments/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { CommentService } from './CommentService'
|
||||
|
||||
export {
|
||||
CommentService
|
||||
}
|
||||
16
src/data/firestoreClient/services/common/CommonService.ts
Normal file
16
src/data/firestoreClient/services/common/CommonService.ts
Normal 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 {
|
||||
|
||||
}
|
||||
5
src/data/firestoreClient/services/common/index.ts
Normal file
5
src/data/firestoreClient/services/common/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { CommonService } from './CommonService'
|
||||
|
||||
export {
|
||||
CommonService
|
||||
}
|
||||
41
src/data/firestoreClient/services/files/StorageService.ts
Normal file
41
src/data/firestoreClient/services/files/StorageService.ts
Normal 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)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
5
src/data/firestoreClient/services/files/index.ts
Normal file
5
src/data/firestoreClient/services/files/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { StorageService } from './StorageService'
|
||||
|
||||
export {
|
||||
StorageService
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
5
src/data/firestoreClient/services/imageGallery/index.ts
Normal file
5
src/data/firestoreClient/services/imageGallery/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { ImageGalleryService } from './ImageGalleryService'
|
||||
|
||||
export {
|
||||
ImageGalleryService
|
||||
}
|
||||
24
src/data/firestoreClient/services/index.ts
Normal file
24
src/data/firestoreClient/services/index.ts
Normal 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
|
||||
|
||||
}
|
||||
5
src/data/firestoreClient/services/notifications/index.ts
Normal file
5
src/data/firestoreClient/services/notifications/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { NotificationService } from './NotificationService'
|
||||
|
||||
export {
|
||||
NotificationService
|
||||
}
|
||||
@@ -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))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
105
src/data/firestoreClient/services/posts/PostService.ts
Normal file
105
src/data/firestoreClient/services/posts/PostService.ts
Normal 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))
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
5
src/data/firestoreClient/services/posts/index.ts
Normal file
5
src/data/firestoreClient/services/posts/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { PostService } from './PostService'
|
||||
|
||||
export {
|
||||
PostService
|
||||
}
|
||||
92
src/data/firestoreClient/services/users/UserService.ts
Normal file
92
src/data/firestoreClient/services/users/UserService.ts
Normal 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))
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
5
src/data/firestoreClient/services/users/index.ts
Normal file
5
src/data/firestoreClient/services/users/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { UserService } from './UserService'
|
||||
|
||||
export {
|
||||
UserService
|
||||
}
|
||||
106
src/data/firestoreClient/services/votes/VoteService.ts
Normal file
106
src/data/firestoreClient/services/votes/VoteService.ts
Normal 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))
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
5
src/data/firestoreClient/services/votes/index.ts
Normal file
5
src/data/firestoreClient/services/votes/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { VoteService } from './VoteService'
|
||||
|
||||
export {
|
||||
VoteService
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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}
|
||||
|
||||
@@ -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
40
src/routes/HomeRouter.tsx
Normal 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
35
src/routes/IRoute.ts
Normal 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
|
||||
|
||||
}
|
||||
26
src/routes/IRouterProps.ts
Normal file
26
src/routes/IRouterProps.ts
Normal 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
|
||||
|
||||
}
|
||||
39
src/routes/MasterRouter.tsx
Normal file
39
src/routes/MasterRouter.tsx
Normal 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))
|
||||
29
src/routes/PrivateRoute.tsx
Normal file
29
src/routes/PrivateRoute.tsx
Normal 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)
|
||||
29
src/routes/PublicRoute.tsx
Normal file
29
src/routes/PublicRoute.tsx
Normal 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
7
src/routes/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import MasterRouter from './MasterRouter'
|
||||
import HomeRouter from './HomeRouter'
|
||||
|
||||
export {
|
||||
MasterRouter,
|
||||
HomeRouter
|
||||
}
|
||||
Reference in New Issue
Block a user