[New Feature] Add firestore to data layer

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

View File

@@ -1,10 +1,11 @@
{ {
"plugins": [ "plugins": [
"react-hot-loader/babel", "react-hot-loader/babel",
"transform-decorators-legacy" "transform-decorators-legacy",
"transform-runtime"
], ],
"presets": [ "presets": [
"babel-polyfill", ["env", { "modules": false }], ["env", { "modules": false }],
"react", "react",
"stage-0" "stage-0"
] ]

View File

@@ -8,15 +8,17 @@
"build": "NODE_ENV=production webpack -p", "build": "NODE_ENV=production webpack -p",
"watch": "webpack -w", "watch": "webpack -w",
"deploy:firebase": "npm run build && firebase deploy", "deploy:firebase": "npm run build && firebase deploy",
"start": "npm run build && node server.js" "start": "node server.js"
}, },
"author": "Amir Movahedi", "author": "Amir Movahedi",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@types/react-hot-loader": "^3.0.5", "@types/react-hot-loader": "^3.0.5",
"@types/react-infinite-scroller": "^1.0.4",
"amazon-cognito-identity-js": "^1.21.0", "amazon-cognito-identity-js": "^1.21.0",
"aws-sdk": "^2.132.0", "aws-sdk": "^2.132.0",
"axios": "^0.16.1", "axios": "^0.16.1",
"babel-runtime": "^6.26.0",
"classnames": "^2.2.5", "classnames": "^2.2.5",
"crypto-js": "^3.1.9-1", "crypto-js": "^3.1.9-1",
"css-loader": "^0.28.0", "css-loader": "^0.28.0",
@@ -25,7 +27,7 @@
"express": "^4.15.2", "express": "^4.15.2",
"faker": "^4.1.0", "faker": "^4.1.0",
"file-loader": "^0.11.1", "file-loader": "^0.11.1",
"firebase": "^3.9.0", "firebase": "^4.6.2",
"inversify": "^4.3.0", "inversify": "^4.3.0",
"keycode": "^2.1.9", "keycode": "^2.1.9",
"lodash": "^4.17.4", "lodash": "^4.17.4",
@@ -41,6 +43,7 @@
"react-dom": "^16.0.0", "react-dom": "^16.0.0",
"react-event-listener": "^0.5.1", "react-event-listener": "^0.5.1",
"react-hot-loader": "^3.1.3", "react-hot-loader": "^3.1.3",
"react-infinite-scroller": "^1.1.1",
"react-linkify": "^0.2.1", "react-linkify": "^0.2.1",
"react-parallax": "^1.4.4", "react-parallax": "^1.4.4",
"react-redux": "^5.0.6", "react-redux": "^5.0.6",
@@ -78,6 +81,7 @@
"babel-core": "^6.24.1", "babel-core": "^6.24.1",
"babel-loader": "^7.1.2", "babel-loader": "^7.1.2",
"babel-plugin-transform-decorators-legacy": "^1.3.4", "babel-plugin-transform-decorators-legacy": "^1.3.4",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"babel-preset-env": "^1.6.0", "babel-preset-env": "^1.6.0",
"babel-preset-react": "^6.24.1", "babel-preset-react": "^6.24.1",
@@ -91,6 +95,7 @@
"karma-sourcemap-loader": "^0.3.7", "karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^2.0.3", "karma-webpack": "^2.0.3",
"mocha": "^3.2.0", "mocha": "^3.2.0",
"open-browser-webpack-plugin": "0.0.5",
"redux-logger": "^3.0.1", "redux-logger": "^3.0.1",
"redux-mock-store": "^1.2.3", "redux-mock-store": "^1.2.3",
"source-map-loader": "^0.2.2", "source-map-loader": "^0.2.2",

View File

@@ -122,7 +122,7 @@
</div> </div>
</div> </div>
<script src="/bundle-v0.1.js"></script> <script src="/bundle-v0.3.js"></script>
</body> </body>
</html> </html>

View File

@@ -3,6 +3,7 @@ import moment from 'moment'
// - Import domain // - Import domain
import { Comment } from 'core/domain/comments' import { Comment } from 'core/domain/comments'
import { Post } from 'core/domain/posts'
import { SocialError } from 'core/domain/common' import { SocialError } from 'core/domain/common'
// - Import action types // - Import action types
@@ -11,6 +12,7 @@ import { CommentActionType } from 'constants/commentActionType'
// - Import actions // - Import actions
import * as globalActions from 'actions/globalActions' import * as globalActions from 'actions/globalActions'
import * as notifyActions from 'actions/notifyActions' import * as notifyActions from 'actions/notifyActions'
import * as postActions from 'actions/postActions'
import { IServiceProvider, ServiceProvide } from 'core/factories' import { IServiceProvider, ServiceProvide } from 'core/factories'
import { ICommentService } from 'core/services/comments' import { ICommentService } from 'core/services/comments'
@@ -43,7 +45,7 @@ export const dbAddComment = (ownerPostUserId: string | null,newComment: Comment,
text: newComment.text text: newComment.text
} }
return commentService.addComment(newComment.postId,comment) return commentService.addComment(comment)
.then((commentKey: string) => { .then((commentKey: string) => {
dispatch(addComment({id: commentKey! ,...comment})) dispatch(addComment({id: commentKey! ,...comment}))
callBack() callBack()
@@ -70,12 +72,42 @@ export const dbAddComment = (ownerPostUserId: string | null,newComment: Comment,
/** /**
* Get all comments from database * Get all comments from database
*/ */
export const dbGetComments = () => { export const dbGetComments = (ownerUserId: string, postId: string) => {
return (dispatch: any, getState: Function) => { return (dispatch: any, getState: Function) => {
let uid: string = getState().authorize.uid let uid: string = getState().authorize.uid
if (uid) { if (uid) {
return commentService.getComments((comments: {[postId: string]: {[commentId: string]: Comment}}) => { return commentService.getComments(postId, (comments: {[postId: string]: {[commentId: string]: Comment}}) => {
/**
* Workout getting the number of post's comment and getting three last comments
*/
dispatch(addCommentList(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 userId: uid
} }
return commentService.updateComment(id,postId,updatedComment) return commentService.updateComment(updatedComment)
.then(() => { .then(() => {
dispatch(updateComment( id, postId, text)) dispatch(updateComment( id, postId, text))
dispatch(globalActions.hideTopLoading()) dispatch(globalActions.hideTopLoading())
@@ -118,7 +150,6 @@ export const dbUpdateComment = (id: string, postId: string, text: string) => {
}) })
} }
} }
/** /**

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,6 +10,14 @@ export interface ICommentGroupComponentProps {
*/ */
comments?: {[commentId: string]: Comment} 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 * The post identifier which comment belong to
* *

View File

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

View File

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

View File

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

View File

@@ -8,13 +8,9 @@ import Snackbar from 'material-ui/Snackbar'
import LinearProgress from 'material-ui/LinearProgress' import LinearProgress from 'material-ui/LinearProgress'
// - Import components // - Import components
import Home from 'components/home'
import 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 MasterLoading from 'components/masterLoading'
import MasterRouter from 'routes/MasterRouter'
import { IMasterComponentProps } from './IMasterComponentProps' import { IMasterComponentProps } from './IMasterComponentProps'
import { IMasterComponentState } from './IMasterComponentState' import { IMasterComponentState } from './IMasterComponentState'
import { ServiceProvide, IServiceProvider } from 'core/factories' import { ServiceProvide, IServiceProvider } from 'core/factories'
@@ -80,7 +76,7 @@ export class MasterComponent extends Component<IMasterComponentProps, IMasterCom
console.log('====================================') console.log('====================================')
} }
componentWillMount () { componentDidMount () {
this._authourizeService.onAuthStateChanged((isVerifide: boolean, user: any) => { this._authourizeService.onAuthStateChanged((isVerifide: boolean, user: any) => {
const {global, clearData, loadDataGuest, defaultDataDisable, defaultDataEnable, login, logout } = this.props const {global, clearData, loadDataGuest, defaultDataDisable, defaultDataEnable, login, logout } = this.props
@@ -114,9 +110,9 @@ export class MasterComponent extends Component<IMasterComponentProps, IMasterCom
* *
* @memberof Master * @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 const { loading, isVerifide } = this.state
return ( return (
@@ -129,25 +125,7 @@ export class MasterComponent extends Component<IMasterComponentProps, IMasterCom
<div className='title'>Loading ... </div> <div className='title'>Loading ... </div>
</div> </div>
<MasterLoading activeLoading={loading} handleLoading={this.handleLoading} /> <MasterLoading activeLoading={loading} handleLoading={this.handleLoading} />
<MasterRouter enabled={!loading} data={{uid}} />
{(!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>)
: ''
}
<Snackbar <Snackbar
open={this.props.global.messageOpen} open={this.props.global.messageOpen}
message={this.props.global.message} message={this.props.global.message}

View File

@@ -1,74 +1,23 @@
import { Comment } from 'core/domain/comments'
import { Post } from 'core/domain/posts/post'
export interface IPostComponentProps { 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 post: Post
/**
* If post is only [0]text, [1]whith picture, ... /**
*/ * Owner's post avatar
postTypeId: string *
/** * @type {string}
* The number votes on a post * @memberof IPostComponentProps
*/ */
score: number avatar: string
/**
* 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
/** /**
* User full name * User full name
@@ -78,14 +27,6 @@ export interface IPostComponentProps {
*/ */
fullName?: string fullName?: string
/**
* Number of comments on the post
*
* @type {number}
* @memberof IPostComponentProps
*/
commentCount?: number
/** /**
* Number of vote on a post * Number of vote on a post
* *
@@ -100,7 +41,7 @@ export interface IPostComponentProps {
* @type {boolean} * @type {boolean}
* @memberof IPostComponentProps * @memberof IPostComponentProps
*/ */
userVoteStatus?: boolean currentUserVote?: boolean
/** /**
* Current user is the owner of the post {true} or not {false} * Current user is the owner of the post {true} or not {false}
@@ -158,4 +99,19 @@ export interface IPostComponentProps {
* @memberof IPostComponentProps * @memberof IPostComponentProps
*/ */
setHomeTitle?: (title: string) => any setHomeTitle?: (title: string) => any
/**
* Get the comments of a post
*
* @memberof IPostComponentProps
*/
getPostComments: (ownerUserId: string, postId: string) => any
/**
* Commnets
*
* @type {{[commentId: string]: Comment}}
* @memberof ICommentGroupComponentProps
*/
commentList?: {[commentId: string]: Comment}
} }

View File

@@ -40,6 +40,7 @@ import UserAvatar from 'components/userAvatar'
// - Import actions // - Import actions
import * as voteActions from 'actions/voteActions' import * as voteActions from 'actions/voteActions'
import * as postActions from 'actions/postActions' import * as postActions from 'actions/postActions'
import * as commentActions from 'actions/commentActions'
import * as globalActions from 'actions/globalActions' import * as globalActions from 'actions/globalActions'
import { IPostComponentProps } from './IPostComponentProps' import { IPostComponentProps } from './IPostComponentProps'
import { IPostComponentState } from './IPostComponentState' import { IPostComponentState } from './IPostComponentState'
@@ -47,80 +48,6 @@ import { IPostComponentState } from './IPostComponentState'
// - Create component class // - Create component class
export class PostComponent extends Component<IPostComponentProps,IPostComponentState> { 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 = { styles = {
counter: { counter: {
lineHeight: '36px', lineHeight: '36px',
@@ -155,11 +82,12 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
*/ */
constructor (props: IPostComponentProps) { constructor (props: IPostComponentProps) {
super(props) super(props)
const {post} = props
this.state = { this.state = {
/** /**
* Post text * Post text
*/ */
text: this.props.body, text: post.body!,
/** /**
* It's true if whole the text post is visible * 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 * 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 * If it's true share will be disabled on post
*/ */
disableSharing: this.props.disableSharing, disableSharing: post.disableSharing!,
/** /**
* Title of share post * 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 * @param {event} evt passed by clicking on comment slide show
*/ */
handleOpenComments = () => { handleOpenComments = () => {
const { getPostComments, commentList, post} = this.props
const {id, ownerUserId} = post
if (!commentList) {
getPostComments(ownerUserId!, id!)
}
this.setState({ this.setState({
openComments: !this.state.openComments openComments: !this.state.openComments
}) })
@@ -248,7 +181,8 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
* @memberof Post * @memberof Post
*/ */
handleDelete = () => { 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 * @memberof Post
*/ */
handleVote = () => { handleVote = () => {
if (this.props.userVoteStatus) { if (this.props.currentUserVote) {
this.props.unvote!() this.props.unvote!()
} else { } else {
this.props.vote!() this.props.vote!()
@@ -330,23 +264,24 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render () { render () {
const {post ,setHomeTitle, goTo, fullName, isPostOwner, commentList, avatar} = this.props
const RightIconMenu = () => ( const RightIconMenu = () => (
<IconMenu iconButtonElement={IconButtonElement} style={{ display: 'block', position: 'absolute', top: '0px', right: '4px' }}> <IconMenu iconButtonElement={IconButtonElement} style={{ display: 'block', position: 'absolute', top: '0px', right: '4px' }}>
<MenuItem primaryText='Edit' onClick={this.handleOpenPostWrite} /> <MenuItem primaryText='Edit' onClick={this.handleOpenPostWrite} />
<MenuItem primaryText='Delete' onClick={this.handleDelete} /> <MenuItem primaryText='Delete' onClick={this.handleDelete} />
<MenuItem primaryText={this.props.disableComments ? 'Enable comments' : 'Disable comments'} onClick={() => this.props.toggleDisableComments!(!this.props.disableComments)} /> <MenuItem primaryText={post.disableComments ? 'Enable comments' : 'Disable comments'} onClick={() => this.props.toggleDisableComments!(!post.disableComments)} />
<MenuItem primaryText={this.props.disableSharing ? 'Enable sharing' : 'Disable sharing'} onClick={() => this.props.toggleSharingComments!(!this.props.disableSharing)} /> <MenuItem primaryText={post.disableSharing ? 'Enable sharing' : 'Disable sharing'} onClick={() => this.props.toggleSharingComments!(!post.disableSharing)} />
</IconMenu> </IconMenu>
) )
const {ownerUserId,setHomeTitle, goTo, ownerDisplayName,creationDate, avatar, fullName, isPostOwner,image, body} = this.props const {ownerUserId, ownerDisplayName, creationDate, image, body} = post
// Define variables // Define variables
return ( return (
<Card> <Card>
<CardHeader <CardHeader
title={<NavLink to={`/${ownerUserId}`}>{ownerDisplayName}</NavLink>} 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>} avatar={<NavLink to={`/${ownerUserId}`}><UserAvatar fullName={fullName!} fileName={avatar!} size={36} /></NavLink>}
> >
{isPostOwner ? ( <div style={this.styles.rightIconMenu as any}><RightIconMenu /></div>) : ''} {isPostOwner ? ( <div style={this.styles.rightIconMenu as any}><RightIconMenu /></div>) : ''}
@@ -384,25 +319,25 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
<Checkbox <Checkbox
checkedIcon={<SvgFavorite style={{fill: '#4CAF50'}}/>} checkedIcon={<SvgFavorite style={{fill: '#4CAF50'}}/>}
uncheckedIcon={<SvgFavoriteBorder style={{fill: '#757575'}} />} uncheckedIcon={<SvgFavoriteBorder style={{fill: '#757575'}} />}
defaultChecked={this.props.userVoteStatus} checked={this.props.currentUserVote}
style={{transform: 'translate(6px, 6px)'}} style={{transform: 'translate(6px, 6px)'}}
/> />
</div> </div>
<div style={this.styles.counter}> {this.props.voteCount! > 0 ? this.props.voteCount : ''} </div> <div style={this.styles.counter}> {this.props.voteCount! > 0 ? this.props.voteCount : ''} </div>
</div> </div>
<div style={{ display: 'flex' }}> <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 <SvgComment viewBox='0 -9 24 34' style={{ height: '30px', width: '30px' }} /> 3
</FloatingActionButton> </FloatingActionButton>
<div style={this.styles.counter}>{this.props.commentCount! > 0 ? this.props.commentCount : ''} </div></div>) : ''} <div style={this.styles.counter}>{post.commentCounter! > 0 ? post.commentCounter : ''} </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}> {!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' }} /> <SvgShare viewBox='0 -9 24 34' style={{ height: '30px', width: '30px' }} />
</FloatingActionButton>) : ''} </FloatingActionButton>) : ''}
</div> </div>
</div> </div>
</CardActions> </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*/} {/* Copy link dialog*/}
<Dialog <Dialog
@@ -421,7 +356,7 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
<MenuItem primaryText='Copy Link' leftIcon={<SvgLink />} onClick={this.handleCopyLink} /> <MenuItem primaryText='Copy Link' leftIcon={<SvgLink />} onClick={this.handleCopyLink} />
</Menu> </Menu>
</Paper>) </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> </Dialog>
@@ -429,11 +364,7 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
open={this.state.openPostWrite} open={this.state.openPostWrite}
onRequestClose={this.handleClosePostWrite} onRequestClose={this.handleClosePostWrite}
edit={true} edit={true}
text= {this.props.body} postModel= {post}
image= {this.props.image ? this.props.image : ''}
id= {this.props.id}
disableComments= {this.props.disableComments}
disableSharing= {this.props.disableSharing}
/> />
</Card> </Card>
@@ -449,14 +380,22 @@ export class PostComponent extends Component<IPostComponentProps,IPostComponentS
* @return {object} props of component * @return {object} props of component
*/ */
const mapDispatchToProps = (dispatch: any, ownProps: IPostComponentProps) => { const mapDispatchToProps = (dispatch: any, ownProps: IPostComponentProps) => {
const {post} = ownProps
return { return {
vote: () => dispatch(voteActions.dbAddVote(ownProps.id,ownProps.ownerUserId)), vote: () => dispatch(voteActions.dbAddVote(post.id!,post.ownerUserId!)),
unvote: () => dispatch(voteActions.dbDeleteVote(ownProps.id)) , unvote: () => dispatch(voteActions.dbDeleteVote(post.id!, post.ownerUserId!)) ,
delete: (id: string) => dispatch(postActions.dbDeletePost(id)), delete: (id: string) => dispatch(postActions.dbDeletePost(id)),
toggleDisableComments: (status: boolean) => dispatch(postActions.dbUpdatePost({id: ownProps.id, disableComments: status}, (x: any) => x)), toggleDisableComments: (status: boolean) => {
toggleSharingComments: (status: boolean) => dispatch(postActions.dbUpdatePost({id: ownProps.id, disableSharing: status},(x: any) => x)), 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)), 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 * @return {object} props of component
*/ */
const mapStateToProps = (state: any, ownProps: IPostComponentProps) => { const mapStateToProps = (state: any, ownProps: IPostComponentProps) => {
const {uid} = state.authorize const {post, vote, authorize, comment} = state
let votes = state.vote.postVotes[ownProps.id] const {uid} = authorize
const post = (state.post.userPosts[uid] ? Object.keys(state.post.userPosts[uid]).filter((key) => { return ownProps.id === key }).length : 0) 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 { return {
avatar: state.user.info && state.user.info[ownProps.ownerUserId] ? state.user.info[ownProps.ownerUserId].avatar || '' : '', commentList,
fullName: state.user.info && state.user.info[ownProps.ownerUserId] ? state.user.info[ownProps.ownerUserId].fullName || '' : '', avatar: state.user.info && state.user.info[ownProps.post.ownerUserId!] ? state.user.info[ownProps.post.ownerUserId!].avatar || '' : '',
commentCount: state.comment.postComments[ownProps.id] ? Object.keys(state.comment.postComments[ownProps.id]).length : 0, fullName: state.user.info && state.user.info[ownProps.post.ownerUserId!] ? state.user.info[ownProps.post.ownerUserId!].fullName || '' : '',
voteCount: state.vote.postVotes[ownProps.id] ? Object.keys(state.vote.postVotes[ownProps.id]).length : 0, voteCount: postModel.score,
userVoteStatus: votes && Object.keys(votes).filter((key) => votes[key].userId === state.authorize.uid)[0] ? true : false, currentUserVote,
isPostOwner: post > 0 isPostOwner: postOwner > 0
} }
} }

View File

@@ -45,39 +45,17 @@ export interface IPostWriteComponentProps {
* @type {string} * @type {string}
* @memberof IPostWriteComponentProps * @memberof IPostWriteComponentProps
*/ */
avatar?: string ownerAvatar?: string
/** /**
* User name * The post owner name
*
* @type {string}
* @memberof IPostWriteComponentProps
*/ */
name?: string ownerDisplayName: string
/** /**
* Post image full path * Post model
*
* @type {string}
* @memberof IPostWriteComponentProps
*/ */
imageFullPath?: string postModel: Post
/**
* 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
/** /**
* Save a post * Save a post

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

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

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

View File

@@ -1,36 +1,37 @@
{ {
"compilerOptions": { "compilerOptions": {
"module": "es6", "module": "es6",
"target": "es6", "target": "es6",
"lib": ["es6","dom"], "lib": ["es6", "dom"],
"types": ["reflect-metadata"], "types": ["reflect-metadata"],
"allowSyntheticDefaultImports": true, // see below "allowSyntheticDefaultImports": true, // see below
"baseUrl": "./src/", // enables you to import relative to this folder "baseUrl": "./src/", // enables you to import relative to this folder
"paths": { "paths": {
"src/*" : ["*"], "src/*": ["*"],
"core/*" : ["core/*"], "core/*": ["core/*"],
"data/*" : ["data/*"], "routes/*": ["routes/*"],
"components/*" : ["components/*"], "data/*": ["data/*"],
"store/*" : ["store/*"], "components/*": ["components/*"],
"api/*" : ["api/*"], "store/*": ["store/*"],
"layouts/*" : ["layouts/*"], "api/*": ["api/*"],
"models/*" : ["models/*"] "layouts/*": ["layouts/*"],
"models/*": ["models/*"]
},
"sourceMap": true, // make TypeScript generate sourcemaps
"outDir": "public", // output directory to build to (irrelevant because we use Webpack most of the time)
"jsx": "preserve", // enable JSX mode, but "preserve" tells TypeScript to not transform it (we'll use Babel)
"strict": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"allowJs": true
}, },
"sourceMap": true, // make TypeScript generate sourcemaps "include": [
"outDir": "public", // output directory to build to (irrelevant because we use Webpack most of the time) "src/**/*"
"jsx": "preserve", // enable JSX mode, but "preserve" tells TypeScript to not transform it (we'll use Babel) ],
"strict": true, "exclude": [
"moduleResolution": "node", "node_modules"
"experimentalDecorators": true, ]
"emitDecoratorMetadata": true,
"allowJs": true
},
"include":[
"src/**/*"
],
"exclude": [
"node_modules"
]
} }

View File

@@ -1,6 +1,7 @@
var webpack = require('webpack'); var webpack = require('webpack');
var path = require('path'); var path = require('path');
var envFile = require('node-env-file'); var envFile = require('node-env-file');
var OpenBrowserPlugin = require('open-browser-webpack-plugin');
process.env.NODE_ENV = process.env.NODE_ENV || 'development'; process.env.NODE_ENV = process.env.NODE_ENV || 'development';
@@ -41,6 +42,7 @@ module.exports = {
}) })
] : [ ] : [
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
new OpenBrowserPlugin({ url: `http://localhost:${process.env.PORT}` }),
new webpack.DefinePlugin({ new webpack.DefinePlugin({
'process.env': { 'process.env': {
NODE_ENV: JSON.stringify(process.env.NODE_ENV), NODE_ENV: JSON.stringify(process.env.NODE_ENV),
@@ -50,14 +52,15 @@ module.exports = {
STORAGE_BUCKET: JSON.stringify(process.env.STORAGE_BUCKET), STORAGE_BUCKET: JSON.stringify(process.env.STORAGE_BUCKET),
PROJECT_ID: JSON.stringify(process.env.PROJECT_ID), PROJECT_ID: JSON.stringify(process.env.PROJECT_ID),
MESSAGING_SENDER_ID: JSON.stringify(process.env.MESSAGING_SENDER_ID), MESSAGING_SENDER_ID: JSON.stringify(process.env.MESSAGING_SENDER_ID),
HOST_URL: JSON.stringify(process.env.HOST_URL) HOST_URL: JSON.stringify(process.env.HOST_URL),
PORT: JSON.stringify(process.env.PORT)
} }
}) })
], ],
output: { output: {
publicPath: '/', publicPath: '/',
path: path.resolve(__dirname, './public'), path: path.resolve(__dirname, './public'),
filename: 'bundle-v0.1.js', filename: 'bundle-v0.3.js',
}, },
resolve: { resolve: {
@@ -75,6 +78,7 @@ module.exports = {
data: 'src/data', data: 'src/data',
api: 'src/api', api: 'src/api',
layouts: 'src/layouts', layouts: 'src/layouts',
routes: 'src/routes',
models: 'src/models', models: 'src/models',
store: 'src/store', store: 'src/store',
applicationStyles: 'src/styles/app.scss', applicationStyles: 'src/styles/app.scss',