diff --git a/.babelrc b/.babelrc index 52c0922..25f2184 100644 --- a/.babelrc +++ b/.babelrc @@ -1,10 +1,11 @@ { "plugins": [ "react-hot-loader/babel", - "transform-decorators-legacy" + "transform-decorators-legacy", + "transform-runtime" ], "presets": [ - "babel-polyfill", ["env", { "modules": false }], + ["env", { "modules": false }], "react", "stage-0" ] diff --git a/package.json b/package.json index fd8d01d..2ad9c2a 100644 --- a/package.json +++ b/package.json @@ -8,15 +8,17 @@ "build": "NODE_ENV=production webpack -p", "watch": "webpack -w", "deploy:firebase": "npm run build && firebase deploy", - "start": "npm run build && node server.js" + "start": "node server.js" }, "author": "Amir Movahedi", "license": "MIT", "dependencies": { "@types/react-hot-loader": "^3.0.5", + "@types/react-infinite-scroller": "^1.0.4", "amazon-cognito-identity-js": "^1.21.0", "aws-sdk": "^2.132.0", "axios": "^0.16.1", + "babel-runtime": "^6.26.0", "classnames": "^2.2.5", "crypto-js": "^3.1.9-1", "css-loader": "^0.28.0", @@ -25,7 +27,7 @@ "express": "^4.15.2", "faker": "^4.1.0", "file-loader": "^0.11.1", - "firebase": "^3.9.0", + "firebase": "^4.6.2", "inversify": "^4.3.0", "keycode": "^2.1.9", "lodash": "^4.17.4", @@ -41,6 +43,7 @@ "react-dom": "^16.0.0", "react-event-listener": "^0.5.1", "react-hot-loader": "^3.1.3", + "react-infinite-scroller": "^1.1.1", "react-linkify": "^0.2.1", "react-parallax": "^1.4.4", "react-redux": "^5.0.6", @@ -78,6 +81,7 @@ "babel-core": "^6.24.1", "babel-loader": "^7.1.2", "babel-plugin-transform-decorators-legacy": "^1.3.4", + "babel-plugin-transform-runtime": "^6.23.0", "babel-polyfill": "^6.26.0", "babel-preset-env": "^1.6.0", "babel-preset-react": "^6.24.1", @@ -91,6 +95,7 @@ "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^2.0.3", "mocha": "^3.2.0", + "open-browser-webpack-plugin": "0.0.5", "redux-logger": "^3.0.1", "redux-mock-store": "^1.2.3", "source-map-loader": "^0.2.2", diff --git a/public/index.html b/public/index.html index d463c7c..d2512c7 100644 --- a/public/index.html +++ b/public/index.html @@ -122,7 +122,7 @@ - + \ No newline at end of file diff --git a/src/actions/commentActions.ts b/src/actions/commentActions.ts index 0bc8513..ad7a084 100644 --- a/src/actions/commentActions.ts +++ b/src/actions/commentActions.ts @@ -3,6 +3,7 @@ import moment from 'moment' // - Import domain import { Comment } from 'core/domain/comments' +import { Post } from 'core/domain/posts' import { SocialError } from 'core/domain/common' // - Import action types @@ -11,6 +12,7 @@ import { CommentActionType } from 'constants/commentActionType' // - Import actions import * as globalActions from 'actions/globalActions' import * as notifyActions from 'actions/notifyActions' +import * as postActions from 'actions/postActions' import { IServiceProvider, ServiceProvide } from 'core/factories' import { ICommentService } from 'core/services/comments' @@ -43,7 +45,7 @@ export const dbAddComment = (ownerPostUserId: string | null,newComment: Comment, text: newComment.text } - return commentService.addComment(newComment.postId,comment) + return commentService.addComment(comment) .then((commentKey: string) => { dispatch(addComment({id: commentKey! ,...comment})) callBack() @@ -70,12 +72,42 @@ export const dbAddComment = (ownerPostUserId: string | null,newComment: Comment, /** * Get all comments from database */ -export const dbGetComments = () => { +export const dbGetComments = (ownerUserId: string, postId: string) => { return (dispatch: any, getState: Function) => { let uid: string = getState().authorize.uid if (uid) { - return commentService.getComments((comments: {[postId: string]: {[commentId: string]: Comment}}) => { + return commentService.getComments(postId, (comments: {[postId: string]: {[commentId: string]: Comment}}) => { + + /** + * Workout getting the number of post's comment and getting three last comments + */ dispatch(addCommentList(comments)) + let commentsCount: number + const state = getState() + const post: Post = state.post.userPosts[ownerUserId][postId] + if (!post) { + return + } + + if (comments && Object.keys(comments).length > 0) { + commentsCount = Object.keys(comments).length + let sortedObjects = comments as any + // Sort posts with creation date + sortedObjects.sort((a: any, b: any) => { + return parseInt(b.creationDate, 10) - parseInt(a.creationDate, 10) + }) + if (!post.comments) { + post.comments = {} + } + Object.keys(sortedObjects).slice(0, 3).forEach((commentId) => { + post.comments![commentId] = { + id: commentId, + ...sortedObjects[commentId] + } + }) + + dispatch(postActions.updatePost(post.ownerUserId!,post)) + } }) } } @@ -107,7 +139,7 @@ export const dbUpdateComment = (id: string, postId: string, text: string) => { userId: uid } - return commentService.updateComment(id,postId,updatedComment) + return commentService.updateComment(updatedComment) .then(() => { dispatch(updateComment( id, postId, text)) dispatch(globalActions.hideTopLoading()) @@ -118,7 +150,6 @@ export const dbUpdateComment = (id: string, postId: string, text: string) => { }) } - } /** diff --git a/src/actions/globalActions.ts b/src/actions/globalActions.ts index 57000b7..d4f874c 100644 --- a/src/actions/globalActions.ts +++ b/src/actions/globalActions.ts @@ -30,7 +30,7 @@ export const defaultDataEnable = () => { /** * Default data loaded status will be false - * @param {boolean} status + * @param {boolean} status */ export const defaultDataDisable = () => { return{ @@ -56,6 +56,7 @@ export const showNotificationSuccess = () => { * Hide global message */ export const hideMessage = () => { + hideTopLoading() return{ type: GlobalActionType.HIDE_MESSAGE_GLOBAL } diff --git a/src/actions/postActions.ts b/src/actions/postActions.ts index b5165fd..6fdb07a 100644 --- a/src/actions/postActions.ts +++ b/src/actions/postActions.ts @@ -3,6 +3,7 @@ import { Action } from 'redux' // - Import domain import { Post } from 'core/domain/posts' +import { Comment } from 'core/domain/comments' import { SocialError } from 'core/domain/common' // - Import utility components @@ -27,7 +28,7 @@ const postService: IPostService = serviceProvider.createPostService() * @param {any} newPost * @param {Function} callBack */ -export let dbAddPost = (newPost: any, callBack: Function) => { +export let dbAddPost = (newPost: Post, callBack: Function) => { return (dispatch: any, getState: Function) => { let uid: string = getState().authorize.uid @@ -39,11 +40,13 @@ export let dbAddPost = (newPost: any, callBack: Function) => { viewCount: 0, body: newPost.body, ownerUserId: uid, - ownerDisplayName: newPost.name, - ownerAvatar: newPost.avatar, + ownerDisplayName: newPost.ownerDisplayName, + ownerAvatar: newPost.ownerAvatar, lastEditDate: 0, tags: newPost.tags || [], commentCounter: 0, + comments: {}, + votes: {}, image: '', imageFullPath: '', video: '', @@ -52,14 +55,14 @@ export let dbAddPost = (newPost: any, callBack: Function) => { deleted: false } - return postService.addPost(uid,post).then((postKey: string) => { + return postService.addPost(post).then((postKey: string) => { dispatch(addPost(uid, { ...post, id: postKey })) callBack() }) - .catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message))) + .catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message))) } } @@ -95,7 +98,7 @@ export const dbAddImagePost = (newPost: Post, callBack: Function) => { deleted: false } - return postService.addPost(uid,post).then((postKey: string) => { + return postService.addPost(post).then((postKey: string) => { dispatch(addPost(uid, { ...post, id: postKey @@ -104,7 +107,7 @@ export const dbAddImagePost = (newPost: Post, callBack: Function) => { dispatch(globalActions.hideTopLoading()) }) - .catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message))) + .catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message))) } @@ -115,51 +118,25 @@ export const dbAddImagePost = (newPost: Post, callBack: Function) => { * @param {object} newPost * @param {func} callBack //TODO: anti pattern should change to parent state or move state to redux */ -export const dbUpdatePost = (newPost: Post, callBack: Function) => { - console.log(newPost) +export const dbUpdatePost = (updatedPost: Post, callBack: Function) => { return (dispatch: any, getState: Function) => { dispatch(globalActions.showTopLoading()) - // Get current user id let uid: string = getState().authorize.uid - // Write the new data simultaneously in the list - let updates: any = {} - let post: Post = getState().post.userPosts[uid][newPost.id!] - let updatedPost: Post = { - postTypeId: post.postTypeId, - creationDate: post.creationDate, - deleteDate: 0, - score: post.score, - viewCount: post.viewCount, - body: newPost.body ? newPost.body : post.body || '', - ownerUserId: uid, - ownerDisplayName: post.ownerDisplayName, - ownerAvatar: post.ownerAvatar, - lastEditDate: moment().unix(), - tags: newPost.tags ? newPost.tags : (post.tags || []), - commentCounter: post.commentCounter, - image: newPost.image ? newPost.image : post.image, - imageFullPath: newPost.imageFullPath!, - video: '', - disableComments: newPost.disableComments !== undefined ? newPost.disableComments : (post.disableComments ? post.disableComments : false), - disableSharing: newPost.disableSharing !== undefined ? newPost.disableSharing : (post.disableSharing ? post.disableSharing : false), - deleted: false - } + return postService.updatePost(updatedPost).then(() => { - return postService.updatePost(uid,newPost.id,updatedPost).then(() => { - - dispatch(updatePost(uid, { id: newPost.id, ...updatedPost })) + dispatch(updatePost(uid, { ...updatedPost })) callBack() dispatch(globalActions.hideTopLoading()) }) - .catch((error: SocialError) => { - dispatch(globalActions.showErrorMessage(error.message)) - dispatch(globalActions.hideTopLoading()) + .catch((error: SocialError) => { + dispatch(globalActions.showErrorMessage(error.message)) + dispatch(globalActions.hideTopLoading()) - }) + }) } } @@ -176,15 +153,15 @@ export const dbDeletePost = (id: string) => { // Get current user id let uid: string = getState().authorize.uid - return postService.deletePost(uid,id).then(() => { + return postService.deletePost(id).then(() => { dispatch(deletePost(uid, id)) dispatch(globalActions.hideTopLoading()) }) - .catch((error: SocialError) => { - dispatch(globalActions.showErrorMessage(error.message)) - dispatch(globalActions.hideTopLoading()) - }) + .catch((error: SocialError) => { + dispatch(globalActions.showErrorMessage(error.message)) + dispatch(globalActions.hideTopLoading()) + }) } } @@ -200,9 +177,9 @@ export const dbGetPosts = () => { return postService.getPosts(uid).then((posts: { [postId: string]: Post }) => { dispatch(addPosts(uid, posts)) }) - .catch((error: SocialError) => { - dispatch(globalActions.showErrorMessage(error.message)) - }) + .catch((error: SocialError) => { + dispatch(globalActions.showErrorMessage(error.message)) + }) } } @@ -217,12 +194,12 @@ export const dbGetPostById = (uid: string, postId: string) => { return (dispatch: any, getState: Function) => { if (uid) { - return postService.getPostById(uid,postId).then((post: Post) => { + return postService.getPostById(postId).then((post: Post) => { dispatch(addPost(uid, post)) }) - .catch((error: SocialError) => { - dispatch(globalActions.showErrorMessage(error.message)) - }) + .catch((error: SocialError) => { + dispatch(globalActions.showErrorMessage(error.message)) + }) } } @@ -233,7 +210,7 @@ export const dbGetPostById = (uid: string, postId: string) => { * @param uid posts owner identifier */ export const dbGetPostsByUserId = (uid: string) => { - return (dispatch: any, getState: Function) => { + return (dispatch: Function, getState: Function) => { if (uid) { return postService.getPosts(uid).then((posts: { [postId: string]: Post }) => { diff --git a/src/actions/userActions.ts b/src/actions/userActions.ts index 2bfa706..dfa6401 100644 --- a/src/actions/userActions.ts +++ b/src/actions/userActions.ts @@ -1,5 +1,5 @@ // - Import react components - +import _ from 'lodash' // - Import domain import { Profile } from 'core/domain/users' import { SocialError } from 'core/domain/common' @@ -26,14 +26,14 @@ export const dbGetUserInfo = () => { return (dispatch: any, getState: Function) => { let uid: string = getState().authorize.uid if (uid) { - return userService.getUserProfile(uid).then((userProfile: Profile) => { dispatch(addUserInfo(uid, { avatar: userProfile.avatar, email: userProfile.email, fullName: userProfile.fullName, banner: userProfile.banner, - tagLine: userProfile.tagLine + tagLine: userProfile.tagLine, + creationDate: userProfile.creationDate })) }) .catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message))) @@ -63,7 +63,8 @@ export const dbGetUserInfoByUserId = (uid: string, callerKey: string) => { email: userProfile.email, fullName: userProfile.fullName, banner: userProfile.banner, - tagLine: userProfile.tagLine + tagLine: userProfile.tagLine, + creationDate: userProfile.creationDate })) switch (callerKey) { @@ -97,7 +98,8 @@ export const dbUpdateUserInfo = (newProfile: Profile) => { banner: newProfile.banner || profile.banner || 'https://firebasestorage.googleapis.com/v0/b/open-social-33d92.appspot.com/o/images%2F751145a1-9488-46fd-a97e-04018665a6d3.JPG?alt=media&token=1a1d5e21-5101-450e-9054-ea4a20e06c57', email: newProfile.email || profile.email || '', fullName: newProfile.fullName || profile.fullName || '', - tagLine: newProfile.tagLine || profile.tagLine || '' + tagLine: newProfile.tagLine || profile.tagLine || '', + creationDate: newProfile.creationDate } return userService.updateUserProfile(uid,updatedProfie).then(() => { @@ -112,11 +114,13 @@ export const dbUpdateUserInfo = (newProfile: Profile) => { } // - Get people info from database -export const dbGetPeopleInfo = () => { +export const dbGetPeopleInfo = (page?: number) => { return (dispatch: any, getState: Function) => { - let uid: string = getState().authorize.uid + const {authorize, user} = getState() + let uid: string = authorize.uid if (uid) { - return userService.getUsersProfile(uid) + const lastKey = '' + return userService.getUsersProfile(uid, lastKey) .then((usersProfile: {[userId: string]: Profile}) => { dispatch(addPeopleInfo(usersProfile)) }) diff --git a/src/actions/voteActions.ts b/src/actions/voteActions.ts index 38338a8..8d09faf 100644 --- a/src/actions/voteActions.ts +++ b/src/actions/voteActions.ts @@ -9,9 +9,11 @@ import { Vote } from 'core/domain/votes' // - Import actions import * as globalActions from 'actions/globalActions' import * as notifyActions from 'actions/notifyActions' +import * as postActions from 'actions/postActions' import { IServiceProvider, ServiceProvide } from 'core/factories' import { IVoteService } from 'core/services/votes' +import { Post } from 'core/domain/posts' const serviceProvider: IServiceProvider = new ServiceProvide() const voteService: IVoteService = serviceProvider.createVoteService() @@ -26,7 +28,8 @@ const voteService: IVoteService = serviceProvider.createVoteService() export const dbAddVote = (postId: string,ownerPostUserId: string) => { return (dispatch: any, getState: Function) => { - let uid: string = getState().authorize.uid + const state = getState() + let uid: string = state.authorize.uid let vote: Vote = { postId: postId, creationDate: moment().unix(), @@ -34,41 +37,52 @@ export const dbAddVote = (postId: string,ownerPostUserId: string) => { userAvatar: getState().user.info[uid].avatar, userId: uid } + const post: Post = state.post.userPosts[ownerPostUserId][postId] + + post.score! += 1 + dispatch(postActions.updatePost(ownerPostUserId,post)) return voteService.addVote(vote).then((voteKey: string) => { - dispatch(addVote( - { - ...vote, - id: voteKey - })) if (uid !== ownerPostUserId) { dispatch(notifyActions.dbAddNotification( { description: 'Vote on your post.', url: `/${ownerPostUserId}/posts/${postId}`, - notifyRecieverUserId: ownerPostUserId,notifierUserId:uid, + notifyRecieverUserId: ownerPostUserId,notifierUserId: uid, isSeen: false })) } }) - .catch((error) => dispatch(globalActions.showErrorMessage(error.message))) - + .catch((error) => { + post.score! -= 1 + dispatch(postActions.updatePost(ownerPostUserId,post)) + dispatch(globalActions.showErrorMessage(error.message)) + }) } } /** * Get all votes from database */ -export const dbGetVotes = () => { +export const dbGetVotes = (userId: string, postId: string) => { return (dispatch: any, getState: Function) => { let uid: string = getState().authorize.uid if (uid) { return voteService - .getVotes() + .getVotes(postId) .then((postVotes: { [postId: string]: { [voteId: string]: Vote } }) => { dispatch(addVoteList(postVotes)) + const state = getState() + const post: Post = state.post.userPosts[userId][postId] + if (!post) { + return + } + const votes = postVotes[postId] + if (votes && Object.keys(votes).length > 0) { + post.score = Object.keys(votes).length + } }) } @@ -80,19 +94,24 @@ export const dbGetVotes = () => { * @param {string} id of vote * @param {string} postId is the identifier of the post which vote belong to */ -export const dbDeleteVote = (postId: string) => { +export const dbDeleteVote = (postId: string, ownerPostUserId: string) => { return (dispatch: any, getState: Function) => { - + const state = getState() // Get current user id - let uid: string = getState().authorize.uid + let uid: string = state.authorize.uid let votes: {[voteId: string]: Vote} = getState().vote.postVotes[postId] let id: string = Object.keys(votes).filter((key) => votes[key].userId === uid)[0] - - return voteService.deleteVote(id,postId).then(() => { - dispatch(deleteVote(id, postId)) + const vote = votes[id] + const post: Post = state.post.userPosts[ownerPostUserId][postId] + post.score! -= 1 + dispatch(postActions.updatePost(ownerPostUserId,post)) + return voteService.deleteVote(vote).then(x => x) + .catch((error: any) => { + post.score! += 1 + dispatch(postActions.updatePost(ownerPostUserId,post)) + dispatch(globalActions.showErrorMessage(error.message)) }) - .catch((error: any) => dispatch(globalActions.showErrorMessage(error.message))) } } @@ -117,7 +136,7 @@ export const deleteVote = (id: string, postId: string) => { /** * Ad a list of vote - * @param {[postId:string]: {[voteId: string]: Vote}} votes a list of vote + * @param {[postId:string]: {[voteId: string]: Vote}} votes a list of vote */ export const addVoteList = (votes: {[postId: string]: {[voteId: string]: Vote}}) => { return { type: VoteActionType.ADD_VOTE_LIST, payload: votes } diff --git a/src/api/FileAPI.ts b/src/api/FileAPI.ts index 9153708..e53e6cd 100644 --- a/src/api/FileAPI.ts +++ b/src/api/FileAPI.ts @@ -1,5 +1,3 @@ -// - Import react component -import { storageRef } from 'data/firebaseClient' // - Interface declaration interface FileReaderEventTarget extends EventTarget { @@ -27,41 +25,6 @@ const convertImageToCanvas = (image: HTMLImageElement | HTMLCanvasElement | HTML return canvas } -/** - * Upload image on the server - * @param {file} file - * @param {string} fileName - */ -const uploadImage = (file: any, fileName: string, progress: (percentage: number, status: boolean) => void) => { - - return new Promise((resolve, reject) => { - // Create a storage refrence - let storegeFile = storageRef.child(`images/${fileName}`) - - // Upload file - let task = storegeFile.put(file) - task.then((result) => { - resolve(result) - }).catch((error) => { - reject(error) - }) - - // Upload storage bar - task.on('state_changed', (snapshot: any) => { - let percentage: number = (snapshot.bytesTransferred / snapshot.totalBytes) * 100 - progress(percentage, true) - }, (error) => { - console.log('========== Upload Image ============') - console.log(error) - console.log('====================================') - - }, () => { - progress(100, false) - }) - }) - -} - /** * Constraint image size * @param {file} file @@ -141,7 +104,6 @@ export default { dataURLToBlob, convertImageToCanvas, getExtension, - constraintImage, - uploadImage + constraintImage } diff --git a/src/components/commentGroup/CommentGroupComponent.tsx b/src/components/commentGroup/CommentGroupComponent.tsx index 430d95d..6e7c7bf 100644 --- a/src/components/commentGroup/CommentGroupComponent.tsx +++ b/src/components/commentGroup/CommentGroupComponent.tsx @@ -7,7 +7,8 @@ import FlatButton from 'material-ui/FlatButton' import TextField from 'material-ui/TextField' import Divider from 'material-ui/Divider' import { ListItem } from 'material-ui/List' -import { grey400, darkBlack, lightBlack } from 'material-ui/styles/colors' +import { grey400, darkBlack, lightBlack, tealA400 } from 'material-ui/styles/colors' +import LinearProgress from 'material-ui/LinearProgress' // - Import actions import * as commentActions from 'actions/commentActions' @@ -65,6 +66,10 @@ export class CommentGroupComponent extends Component { - let comments = this.props.comments + let comments = this.props.commentSlides if (comments) { let parsedComments: Comment[] = [] @@ -178,11 +183,8 @@ export class CommentGroupComponent extends Component} secondaryTextLines={2} /> - ) - }) - } } @@ -191,9 +193,41 @@ export class CommentGroupComponent extends Component + + + +
+ +
+ +
+
+ +
+ ) + /** + * Return Elements + */ return (
-
0 ? { display: 'block' } : { display: 'none' }}> +
0 ? { display: 'block' } : { display: 'none' }}> @@ -206,37 +240,22 @@ export class CommentGroupComponent extends Component
- {(this.props.comments && Object.keys(this.props.comments).length > 0) - ? ( - - - ) : ''} + { + !comments + ? this.props.open ? : '' + : (Object.keys(comments).length > 0 + ? ( + + ) + : '') + }
- {!this.props.disableComments ? (
- - - -
- -
- -
-
- -
-
) : ''} + { + !this.props.disableComments + ? commentWriteBox + : '' + }
) } @@ -266,11 +285,15 @@ const mapDispatchToProps = (dispatch: any, ownProps: ICommentGroupComponentProps * @return {object} props of component */ const mapStateToProps = (state: any, ownProps: ICommentGroupComponentProps) => { + const {post, user, authorize} = state + const {ownerPostUserId, postId} = ownProps + const commentSlides = post.userPosts[ownerPostUserId] && post.userPosts[ownerPostUserId][postId] ? post.userPosts[ownerPostUserId][postId].comments : {} + return { - comments: state.comment.postComments[ownProps.postId], - avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar || '' : '', - fullName: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].fullName || '' : '', - userInfo: state.user.info + commentSlides, + avatar: user.info && user.info[state.authorize.uid] ? user.info[authorize.uid].avatar || '' : '', + fullName: user.info && user.info[state.authorize.uid] ? user.info[authorize.uid].fullName || '' : '', + userInfo: user.info } } diff --git a/src/components/commentGroup/ICommentGroupComponentProps.ts b/src/components/commentGroup/ICommentGroupComponentProps.ts index 5da4e07..a91be07 100644 --- a/src/components/commentGroup/ICommentGroupComponentProps.ts +++ b/src/components/commentGroup/ICommentGroupComponentProps.ts @@ -10,6 +10,14 @@ export interface ICommentGroupComponentProps { */ comments?: {[commentId: string]: Comment} + /** + * Commnets show on slide preview + * + * @type {{[commentId: string]: Comment}} + * @memberof ICommentGroupComponentProps + */ + commentSlides?: {[commentId: string]: Comment} + /** * The post identifier which comment belong to * diff --git a/src/components/findPeople/FindPeopleComponent.tsx b/src/components/findPeople/FindPeopleComponent.tsx index b5c84e6..62ded2a 100644 --- a/src/components/findPeople/FindPeopleComponent.tsx +++ b/src/components/findPeople/FindPeopleComponent.tsx @@ -3,6 +3,7 @@ import React, { Component } from 'react' import { connect } from 'react-redux' import PropTypes from 'prop-types' import Paper from 'material-ui/Paper' +import InfiniteScroll from 'react-infinite-scroller' // - Import app components import UserBoxList from 'components/userBoxList' @@ -39,6 +40,12 @@ export class FindPeopleComponent extends ComponentLoading ... return (
+ + +
+ {this.props.peopleInfo && Object.keys(this.props.peopleInfo).length !== 0 ? (
Suggestions for you @@ -75,6 +91,8 @@ export class FindPeopleComponent extends Component) : (
Nothing to show! :(
)} +
+
) } diff --git a/src/components/home/HomeComponent.tsx b/src/components/home/HomeComponent.tsx index 4dda80e..9a99126 100644 --- a/src/components/home/HomeComponent.tsx +++ b/src/components/home/HomeComponent.tsx @@ -1,4 +1,5 @@ // - Import react components +import { HomeRouter } from 'routes' import React, { Component } from 'react' import _ from 'lodash' import { Route, Switch, withRouter, Redirect, NavLink } from 'react-router-dom' @@ -111,8 +112,7 @@ export class HomeComponent extends Component @@ -153,35 +153,7 @@ export class HomeComponent extends Component - {loaded ? ( - { - return ( - this.props.authed - ? - : - ) - }} /> - { - - return ( - this.props.authed - ?
- : - ) - }} /> - - - - { - - return ( - this.props.authed - ?
- : - ) - }} /> -
) - : ''} +
@@ -196,11 +168,9 @@ const mapDispatchToProps = (dispatch: any, ownProps: IHomeComponentProps) => { return { loadData: () => { - dispatch(commentActions.dbGetComments()) dispatch(imageGalleryActions.dbGetImageGallery()) dispatch(postActions.dbGetPosts()) dispatch(userActions.dbGetUserInfo()) - dispatch(voteActions.dbGetVotes()) dispatch(notifyActions.dbGetNotifications()) dispatch(circleActions.dbGetCircles()) @@ -209,8 +179,6 @@ const mapDispatchToProps = (dispatch: any, ownProps: IHomeComponentProps) => { dispatch(imageGalleryActions.clearAllData()) dispatch(postActions.clearAllData()) dispatch(userActions.clearAllData()) - dispatch(commentActions.clearAllData()) - dispatch(voteActions.clearAllvotes()) dispatch(notifyActions.clearAllNotifications()) dispatch(circleActions.clearAllCircles()) dispatch(globalActions.clearTemp()) @@ -234,7 +202,7 @@ const mapDispatchToProps = (dispatch: any, ownProps: IHomeComponentProps) => { * @return {object} props of component */ const mapStateToProps = (state: any, ownProps: IHomeComponentProps) => { - const { authorize, global, user, post, comment, imageGallery, vote, notify, circle } = state + const { authorize, global, user, post, imageGallery, notify, circle } = state const { uid } = authorize let mergedPosts = {} const circles = circle ? (circle.userCircles[uid] || {}) : {} @@ -251,7 +219,7 @@ const mapStateToProps = (state: any, ownProps: IHomeComponentProps) => { mainStyle: global.sidebarMainStyle, mergedPosts, global, - loaded: user.loaded && post.loaded && comment.loaded && imageGallery.loaded && vote.loaded && notify.loaded && circle.loaded + loaded: user.loaded && post.loaded && imageGallery.loaded && notify.loaded && circle.loaded } } diff --git a/src/components/login/ILoginComponentProps.ts b/src/components/login/ILoginComponentProps.ts index a623adb..b9c6b8b 100644 --- a/src/components/login/ILoginComponentProps.ts +++ b/src/components/login/ILoginComponentProps.ts @@ -13,7 +13,7 @@ export interface ILoginComponentProps { * * @memberof ILoginComponentProps */ - loginWithOAuth: (type: OAuthType) => any + loginWithOAuth?: (type: OAuthType) => any /** * Redirect to signup page diff --git a/src/components/master/MasterComponent.tsx b/src/components/master/MasterComponent.tsx index d852f7c..4a497e3 100644 --- a/src/components/master/MasterComponent.tsx +++ b/src/components/master/MasterComponent.tsx @@ -8,13 +8,9 @@ import Snackbar from 'material-ui/Snackbar' import LinearProgress from 'material-ui/LinearProgress' // - Import components -import Home from 'components/home' -import Signup from 'components/signup' -import EmailVerification from 'components/emailVerification' -import Login from 'components/login' -import ResetPassword from 'components/resetPassword' -import Setting from 'components/setting' + import MasterLoading from 'components/masterLoading' +import MasterRouter from 'routes/MasterRouter' import { IMasterComponentProps } from './IMasterComponentProps' import { IMasterComponentState } from './IMasterComponentState' import { ServiceProvide, IServiceProvider } from 'core/factories' @@ -80,7 +76,7 @@ export class MasterComponent extends Component { const {global, clearData, loadDataGuest, defaultDataDisable, defaultDataEnable, login, logout } = this.props @@ -114,9 +110,9 @@ export class MasterComponent extends Component { + public render () { - const { progress, global, loaded, guest } = this.props + const { progress, global, loaded, guest, uid } = this.props const { loading, isVerifide } = this.state return ( @@ -129,25 +125,7 @@ export class MasterComponent extends ComponentLoading ...
- - {(!loading) ? ( - - - - - { - return ( - this.props.authed - ? - : - ) - } - } /> - } /> - - ) - : '' - } + 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} } diff --git a/src/components/post/PostComponent.tsx b/src/components/post/PostComponent.tsx index d47a847..8568e28 100644 --- a/src/components/post/PostComponent.tsx +++ b/src/components/post/PostComponent.tsx @@ -40,6 +40,7 @@ import UserAvatar from 'components/userAvatar' // - Import actions import * as voteActions from 'actions/voteActions' import * as postActions from 'actions/postActions' +import * as commentActions from 'actions/commentActions' import * as globalActions from 'actions/globalActions' import { IPostComponentProps } from './IPostComponentProps' import { IPostComponentState } from './IPostComponentState' @@ -47,80 +48,6 @@ import { IPostComponentState } from './IPostComponentState' // - Create component class export class PostComponent extends Component { - static propTypes = { - - /** - * The context of a post - */ - body: PropTypes.string, - /** - * The number of comment on a post - */ - commentCounter: PropTypes.number, - /** - * Creation post date - */ - creationDate: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number - ]), - /** - * Post identifier - */ - id: PropTypes.string, - /** - * Post image address - */ - image: PropTypes.string, - /** - * The last time date when post has was edited - */ - lastEditDate: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number - ]), - /** - * The name of the user who created the post - */ - ownerDisplayName: PropTypes.string, - /** - * The identifier of the user who created the post - */ - ownerUserId: PropTypes.string, - /** - * The avatar address of the user who created the post - */ - ownerAvatar: PropTypes.string, - /** - * If post is only [0]text, [1]whith picture, ... - */ - postTypeId: PropTypes.number, - /** - * The number votes on a post - */ - score: PropTypes.number, - /** - * Array of tags on a post - */ - tags: PropTypes.array, - /** - * The video address of a post - */ - video: PropTypes.string, - /** - * If it's true comment will be disabled on a post - */ - disableComments: PropTypes.bool, - /** - * If it's true sharing will be disabled on a post - */ - disableSharing: PropTypes.bool, - /** - * The number of users who has visited the post - */ - viewCount: PropTypes.number - } - styles = { counter: { lineHeight: '36px', @@ -155,11 +82,12 @@ export class PostComponent extends Component { + const { getPostComments, commentList, post} = this.props + const {id, ownerUserId} = post + if (!commentList) { + getPostComments(ownerUserId!, id!) + } this.setState({ openComments: !this.state.openComments }) @@ -248,7 +181,8 @@ export class PostComponent extends Component { - this.props.delete!(this.props.id) + const {post} = this.props + this.props.delete!(post.id!) } /** @@ -297,7 +231,7 @@ export class PostComponent extends Component { - if (this.props.userVoteStatus) { + if (this.props.currentUserVote) { this.props.unvote!() } else { this.props.vote!() @@ -330,23 +264,24 @@ export class PostComponent extends Component ( - this.props.toggleDisableComments!(!this.props.disableComments)} /> - this.props.toggleSharingComments!(!this.props.disableSharing)} /> + this.props.toggleDisableComments!(!post.disableComments)} /> + this.props.toggleSharingComments!(!post.disableSharing)} /> ) - const {ownerUserId,setHomeTitle, goTo, ownerDisplayName,creationDate, avatar, fullName, isPostOwner,image, body} = this.props + const {ownerUserId, ownerDisplayName, creationDate, image, body} = post // Define variables return ( {ownerDisplayName}} - subtitle={moment.unix(creationDate).fromNow() + ' | public'} + subtitle={moment.unix(creationDate!).fromNow() + ' | public'} avatar={} > {isPostOwner ? (
) : ''} @@ -384,25 +319,25 @@ export class PostComponent extends Component} uncheckedIcon={} - defaultChecked={this.props.userVoteStatus} + checked={this.props.currentUserVote} style={{transform: 'translate(6px, 6px)'}} />
{this.props.voteCount! > 0 ? this.props.voteCount : ''}
- {!this.props.disableComments ? (
+ {!post.disableComments ? (
3 -
{this.props.commentCount! > 0 ? this.props.commentCount : ''}
) : ''} - {!this.props.disableSharing ? ( +
{post.commentCounter! > 0 ? post.commentCounter : ''}
) : ''} + {!post.disableSharing ? ( ) : ''}
- + {/* Copy link dialog*/} } onClick={this.handleCopyLink} /> ) - : + : } @@ -429,11 +364,7 @@ export class PostComponent extends Component
@@ -449,14 +380,22 @@ export class PostComponent extends Component { + const {post} = ownProps return { - vote: () => dispatch(voteActions.dbAddVote(ownProps.id,ownProps.ownerUserId)), - unvote: () => dispatch(voteActions.dbDeleteVote(ownProps.id)) , + vote: () => dispatch(voteActions.dbAddVote(post.id!,post.ownerUserId!)), + unvote: () => dispatch(voteActions.dbDeleteVote(post.id!, post.ownerUserId!)) , delete: (id: string) => dispatch(postActions.dbDeletePost(id)), - toggleDisableComments: (status: boolean) => dispatch(postActions.dbUpdatePost({id: ownProps.id, disableComments: status}, (x: any) => x)), - toggleSharingComments: (status: boolean) => dispatch(postActions.dbUpdatePost({id: ownProps.id, disableSharing: status},(x: any) => x)), + toggleDisableComments: (status: boolean) => { + post.disableComments = status + dispatch(postActions.dbUpdatePost(post, (x: any) => x)) + }, + toggleSharingComments: (status: boolean) => { + post.disableSharing = status + dispatch(postActions.dbUpdatePost({id: post.id!, disableSharing: status},(x: any) => x)) + }, goTo: (url: string) => dispatch(push(url)), - setHomeTitle: (title: string) => dispatch(globalActions.setHeaderTitle(title || '')) + setHomeTitle: (title: string) => dispatch(globalActions.setHeaderTitle(title || '')), + getPostComments: (ownerUserId: string, postId: string) => dispatch(commentActions.dbGetComments(ownerUserId,postId)) } } @@ -468,17 +407,20 @@ const mapDispatchToProps = (dispatch: any, ownProps: IPostComponentProps) => { * @return {object} props of component */ const mapStateToProps = (state: any, ownProps: IPostComponentProps) => { - const {uid} = state.authorize - let votes = state.vote.postVotes[ownProps.id] - const post = (state.post.userPosts[uid] ? Object.keys(state.post.userPosts[uid]).filter((key) => { return ownProps.id === key }).length : 0) + const {post, vote, authorize, comment} = state + const {uid} = authorize + let currentUserVote = post.votes ? post.votes[uid] : false + const postModel = post.userPosts[ownProps.post.ownerUserId!][ownProps.post.id!] + const postOwner = (post.userPosts[uid] ? Object.keys(post.userPosts[uid]).filter((key) => { return ownProps.post.id === key }).length : 0) + const commentList: { [commentId: string]: Comment } = comment.postComments[ownProps.post.id!] return { - avatar: state.user.info && state.user.info[ownProps.ownerUserId] ? state.user.info[ownProps.ownerUserId].avatar || '' : '', - fullName: state.user.info && state.user.info[ownProps.ownerUserId] ? state.user.info[ownProps.ownerUserId].fullName || '' : '', - commentCount: state.comment.postComments[ownProps.id] ? Object.keys(state.comment.postComments[ownProps.id]).length : 0, - voteCount: state.vote.postVotes[ownProps.id] ? Object.keys(state.vote.postVotes[ownProps.id]).length : 0, - userVoteStatus: votes && Object.keys(votes).filter((key) => votes[key].userId === state.authorize.uid)[0] ? true : false, - isPostOwner: post > 0 + commentList, + avatar: state.user.info && state.user.info[ownProps.post.ownerUserId!] ? state.user.info[ownProps.post.ownerUserId!].avatar || '' : '', + fullName: state.user.info && state.user.info[ownProps.post.ownerUserId!] ? state.user.info[ownProps.post.ownerUserId!].fullName || '' : '', + voteCount: postModel.score, + currentUserVote, + isPostOwner: postOwner > 0 } } diff --git a/src/components/postWrite/IPostWriteComponentProps.ts b/src/components/postWrite/IPostWriteComponentProps.ts index f24d3d8..5a0c411 100644 --- a/src/components/postWrite/IPostWriteComponentProps.ts +++ b/src/components/postWrite/IPostWriteComponentProps.ts @@ -45,39 +45,17 @@ export interface IPostWriteComponentProps { * @type {string} * @memberof IPostWriteComponentProps */ - avatar?: string + ownerAvatar?: string /** - * User name - * - * @type {string} - * @memberof IPostWriteComponentProps + * The post owner name */ - name?: string + ownerDisplayName: string /** - * Post image full path - * - * @type {string} - * @memberof IPostWriteComponentProps + * Post model */ - imageFullPath?: string - - /** - * Comment on the post is disabled {true} or not {false} - * - * @type {boolean} - * @memberof IPostWriteComponentProps - */ - disableComments?: boolean - - /** - * Sharing on a post is disabled {true} or not {false} - * - * @type {boolean} - * @memberof IPostWriteComponentProps - */ - disableSharing?: boolean + postModel: Post /** * Save a post diff --git a/src/components/postWrite/PostWriteComponent.tsx b/src/components/postWrite/PostWriteComponent.tsx index 0c4a36a..e7f70bd 100644 --- a/src/components/postWrite/PostWriteComponent.tsx +++ b/src/components/postWrite/PostWriteComponent.tsx @@ -34,37 +34,6 @@ import { Post } from 'core/domain/posts' // - Create PostWrite component class export class PostWriteComponent extends Component { - static propTypes = { - /** - * If it's true post writing page will be open - */ - open: PropTypes.bool, - /** - * Recieve request close function - */ - onRequestClose: PropTypes.func, - /** - * Post write style - */ - style: PropTypes.object, - /** - * If it's true, post will be in edit view - */ - edit: PropTypes.bool.isRequired, - /** - * The text of post in editing state - */ - text: PropTypes.string, - /** - * The image of post in editing state - */ - image: PropTypes.string, - /** - * If post state is editing this id sould be filled with post identifier - */ - id: PropTypes.string - - } /** * Component constructor * @param {object} props is an object properties of component @@ -73,6 +42,8 @@ export class PostWriteComponent extends Component { - const { image, imageFullPath, @@ -172,13 +142,21 @@ export class PostWriteComponent extends Component{!this.state.disableSharing ? 'Disable sharing' : 'Enable sharing'} ) - let postAvatar = + let postAvatar = let author = (
@@ -341,7 +319,7 @@ export class PostWriteComponent extends Component{this.props.name}{this.props.ownerDisplayName} | Public diff --git a/src/components/stream/StreamComponent.tsx b/src/components/stream/StreamComponent.tsx index c0cebfe..8bb4ec1 100644 --- a/src/components/stream/StreamComponent.tsx +++ b/src/components/stream/StreamComponent.tsx @@ -161,23 +161,7 @@ export class StreamComponent extends Component {index > 1 || (!postBack.divided && index > 0) ?
: ''} - +
) diff --git a/src/components/userAvatar/UserAvatarComponent.tsx b/src/components/userAvatar/UserAvatarComponent.tsx index 84419f2..4738f51 100644 --- a/src/components/userAvatar/UserAvatarComponent.tsx +++ b/src/components/userAvatar/UserAvatarComponent.tsx @@ -24,11 +24,11 @@ export class UserAvatarComponent extends Component {(fileName && fileName !== '' && fileName !== 'noImage' ) ? ( ) - : ({fullName.slice(0, 1)}) } + : ({fullName ? fullName.slice(0, 1) : ''}) } ) } diff --git a/src/core/domain/notifications/notification.ts b/src/core/domain/notifications/notification.ts index 680007f..ab28c4e 100644 --- a/src/core/domain/notifications/notification.ts +++ b/src/core/domain/notifications/notification.ts @@ -2,6 +2,14 @@ import { BaseDomain } from 'core/domain/common' export class Notification extends BaseDomain { + /** + * Notification identifier + * + * @type {string} + * @memberof Notification + */ + public id?: string + /** * Description of notification * diff --git a/src/core/domain/posts/post.ts b/src/core/domain/posts/post.ts index fb80a02..39fbf97 100644 --- a/src/core/domain/posts/post.ts +++ b/src/core/domain/posts/post.ts @@ -1,5 +1,5 @@ import { BaseDomain } from 'core/domain/common' - +import { Comment } from 'core/domain/comments' export class Post extends BaseDomain { /** @@ -42,6 +42,14 @@ export class Post extends BaseDomain { */ public score?: number + /** + * List of voter identifier + * + * @type {{[voterId: string]: boolean}} + * @memberof Post + */ + votes?: {[voterId: string]: boolean} + /** * Post view count * @@ -50,6 +58,14 @@ export class Post extends BaseDomain { */ public viewCount?: number + /** + * Store three last comments to show in slide preview comment + * + * @type {{[commentId: string]: Comment}} + * @memberof Post + */ + comments?: {[commentId: string]: Comment} + /** * The text of post * @@ -136,7 +152,7 @@ export class Post extends BaseDomain { * @type {Boolean} * @memberof Post */ - public disableComments?: Boolean + public disableComments?: boolean /** * If sharing post is disabled {true} or not {false} @@ -144,7 +160,7 @@ export class Post extends BaseDomain { * @type {Boolean} * @memberof Post */ - public disableSharing?: Boolean + public disableSharing?: boolean /** * If the post is deleted {true} or not false @@ -152,6 +168,6 @@ export class Post extends BaseDomain { * @type {Boolean} * @memberof Post */ - public deleted?: Boolean + public deleted?: boolean } diff --git a/src/core/domain/users/profile.ts b/src/core/domain/users/profile.ts index e66c5b3..dc7cbbe 100644 --- a/src/core/domain/users/profile.ts +++ b/src/core/domain/users/profile.ts @@ -6,7 +6,9 @@ export class Profile extends BaseDomain { public fullName: string, public banner: string, public tagLine: string, - public email?: string | null) { + public creationDate: number, + public email?: string | null + ) { super() } diff --git a/src/core/factories/serviceProvide.ts b/src/core/factories/serviceProvide.ts index f63f55a..aae20c1 100644 --- a/src/core/factories/serviceProvide.ts +++ b/src/core/factories/serviceProvide.ts @@ -30,7 +30,7 @@ import { UserService, VoteService, StorageService -} from 'data/firebaseClient/services' +} from 'data/firestoreClient/services' //#endregion diff --git a/src/core/services/comments/ICommentService.ts b/src/core/services/comments/ICommentService.ts index cb9aff3..1c4ae27 100644 --- a/src/core/services/comments/ICommentService.ts +++ b/src/core/services/comments/ICommentService.ts @@ -10,9 +10,9 @@ import { Comment } from 'core/domain/comments' */ export interface ICommentService { - addComment: (postId: string, comment: Comment) => Promise - getComments: (callback: (resultComments: { [postId: string]: { [commentId: string]: Comment } }) => void) => void - updateComment: (commentId: string, postId: string, comment: Comment) => Promise + addComment: (comment: Comment) => Promise + getComments: (postId: string, callback: (resultComments: { [postId: string]: { [commentId: string]: Comment } }) => void) => void + updateComment: (comment: Comment) => Promise deleteComment: (commentId: string, postId: string) => Promise } diff --git a/src/core/services/posts/IPostService.ts b/src/core/services/posts/IPostService.ts index ab42044..340bc3b 100644 --- a/src/core/services/posts/IPostService.ts +++ b/src/core/services/posts/IPostService.ts @@ -8,9 +8,9 @@ import { Post } from 'core/domain/posts' * @interface IPostService */ export interface IPostService { - addPost: (userId: string, post: Post) => Promise - updatePost: (userId: string, postId: string, post: Post) => Promise - deletePost: (userId: string,postId: string) => Promise + addPost: (post: Post) => Promise + updatePost: (post: Post) => Promise + deletePost: (postId: string) => Promise getPosts: (userId: string) => Promise<{ [postId: string]: Post }> - getPostById: (userId: string, postId: string) => Promise + getPostById: (postId: string) => Promise } diff --git a/src/core/services/users/IUserService.ts b/src/core/services/users/IUserService.ts index 66fb7c6..922dc78 100644 --- a/src/core/services/users/IUserService.ts +++ b/src/core/services/users/IUserService.ts @@ -9,5 +9,5 @@ import { User, Profile } from 'core/domain/users' export interface IUserService { getUserProfile: (userId: string) => Promise updateUserProfile: (userId: string, profile: Profile) => Promise - getUsersProfile: (userId: string) => Promise<{[userId: string]: Profile}> + getUsersProfile: (userId: string, lastKey?: string, numberOfItems?: number) => Promise<{[userId: string]: Profile}> } diff --git a/src/core/services/votes/IVoteService.ts b/src/core/services/votes/IVoteService.ts index c8ca840..087cb53 100644 --- a/src/core/services/votes/IVoteService.ts +++ b/src/core/services/votes/IVoteService.ts @@ -9,6 +9,6 @@ import { Vote } from 'core/domain/votes' */ export interface IVoteService { addVote: (vote: Vote) => Promise - getVotes: () => Promise<{[postId: string]: {[voteId: string]: Vote}}> - deleteVote: (voteId: string, postId: string) => Promise + getVotes: (postId: string) => Promise<{[postId: string]: {[voteId: string]: Vote}}> + deleteVote: (vote: Vote) => Promise } diff --git a/src/data/firebaseClient/index.ts b/src/data/firebaseClient/index.ts index 284178d..7050d70 100644 --- a/src/data/firebaseClient/index.ts +++ b/src/data/firebaseClient/index.ts @@ -1,7 +1,4 @@ -declare const process: any - import firebase from 'firebase' - try { let config = { apiKey: process.env.API_KEY, @@ -12,7 +9,6 @@ try { messagingSenderId: process.env.MESSAGING_SENDER_ID } - console.log(firebase) firebase.initializeApp(config) } catch (error) { console.log('=========Firebase initializer==============') diff --git a/src/data/firebaseClient/services/authorize/AuthorizeService.ts b/src/data/firebaseClient/services/authorize/AuthorizeService.ts index 6668a5a..838da28 100644 --- a/src/data/firebaseClient/services/authorize/AuthorizeService.ts +++ b/src/data/firebaseClient/services/authorize/AuthorizeService.ts @@ -1,3 +1,6 @@ +import moment from 'moment' + +import { Profile } from 'core/domain/users' // - Import react components import { firebaseRef, firebaseAuth } from 'data/firebaseClient' @@ -68,8 +71,8 @@ export class AuthorizeService implements IAuthorizeService { firebaseAuth() .createUserWithEmailAndPassword(user.email as string, user.password as string) .then((signupResult) => { - const {uid, email, displayName, photoURL} = signupResult - this.storeUserInformation(uid,email,displayName,photoURL).then(resolve) + const {uid, email} = signupResult + this.storeUserInformation(uid,email,user.fullName,'').then(resolve) }) .catch((error: any) => reject(new SocialError(error.code, error.message))) }) @@ -107,7 +110,7 @@ export class AuthorizeService implements IAuthorizeService { firebaseAuth().onAuthStateChanged( (user: any) => { let isVerifide = false if (user) { - if (user.emailVerified) { + if (user.emailVerified || user.providerData[0].providerId.trim() !== 'password') { isVerifide = true } else { isVerifide = false @@ -210,15 +213,17 @@ export class AuthorizeService implements IAuthorizeService { * @private * @memberof AuthorizeService */ - private storeUserInformation = (userId: string, email: string, fullName: string, avatar?: string) => { + private storeUserInformation = (userId: string, email: string, fullName: string, avatar: string) => { return new Promise((resolve,reject) => { firebaseRef.child(`users/${userId}/info`) - .set({ - userId, + .set(new Profile( avatar, - email, - fullName - }) + fullName, + '', + '', + moment().unix(), + email + )) .then((result) => { resolve(new RegisterUserResult(userId)) }) diff --git a/src/data/firebaseClient/services/circles/CircleService.ts b/src/data/firebaseClient/services/circles/CircleService.ts index c893b08..38fc11a 100644 --- a/src/data/firebaseClient/services/circles/CircleService.ts +++ b/src/data/firebaseClient/services/circles/CircleService.ts @@ -1,5 +1,5 @@ // - Import react components -import { firebaseRef, firebaseAuth } from 'data/firebaseClient' +import { firebaseRef, firebaseAuth, db } from 'data/firebaseClient' import { SocialError } from 'core/domain/common' import { ICircleService } from 'core/services/circles' @@ -18,13 +18,11 @@ export class CircleService implements ICircleService { public addCircle: (userId: string, circle: Circle) => Promise = (userId, circle) => { return new Promise((resolve,reject) => { + let circleRef = firebaseRef.child(`users/${userId}/circles`).push(circle) circleRef.then(() => { resolve(circleRef.key as string) }) - .catch((error: any) => { - reject(new SocialError(error.code, error.message)) - }) }) diff --git a/src/data/firebaseClient/services/users/UserService.ts b/src/data/firebaseClient/services/users/UserService.ts index e689269..79d345b 100644 --- a/src/data/firebaseClient/services/users/UserService.ts +++ b/src/data/firebaseClient/services/users/UserService.ts @@ -1,5 +1,6 @@ // - Import react components import { firebaseRef, firebaseAuth } from 'data/firebaseClient' +import moment from 'moment' import { SocialError } from 'core/domain/common' import { Profile, UserProvider } from 'core/domain/users' @@ -13,6 +14,7 @@ import { IUserService } from 'core/services/users' * @implements {IUserService} */ export class UserService implements IUserService { + public getUserProfile: (userId: string) => Promise = (userId) => { return new Promise((resolve, reject) => { @@ -23,7 +25,7 @@ export class UserService implements IUserService { if (Object.keys(userProfile).length === 0 && userProfile.constructor === Object) { this.getUserProviderData(userId).then((providerData: UserProvider) => { const {avatar,fullName, email} = providerData - const userProfile = new Profile(avatar,fullName,'','',email) + const userProfile = new Profile(avatar,fullName,'','', moment().unix(),email) resolve(userProfile) this.updateUserProfile(userId,userProfile) }) @@ -52,10 +54,16 @@ export class UserService implements IUserService { }) }) } - public getUsersProfile: (userId: string) - => Promise<{ [userId: string]: Profile }> = (userId) => { + public getUsersProfile: (userId: string, page?: number, lastKey?: string) + => Promise<{ [userId: string]: Profile }> = (userId, page, lastKey) => { return new Promise<{ [userId: string]: Profile }>((resolve, reject) => { - let usersProfileRef: any = firebaseRef.child(`users`) + let usersProfileRef: any + if (page) { + const numberOfItems = (page * 12) + 12 + usersProfileRef = firebaseRef.child(`users`).orderByKey().startAt(lastKey!).limitToFirst(numberOfItems) + } else { + usersProfileRef = firebaseRef.child(`users`).orderByKey() + } usersProfileRef.once('value').then((snapshot: any) => { let usersProfile: any = snapshot.val() || {} diff --git a/src/data/firestoreClient/index.ts b/src/data/firestoreClient/index.ts new file mode 100644 index 0000000..352f378 --- /dev/null +++ b/src/data/firestoreClient/index.ts @@ -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 diff --git a/src/data/firestoreClient/services/authorize/AuthorizeService.ts b/src/data/firestoreClient/services/authorize/AuthorizeService.ts new file mode 100644 index 0000000..a246744 --- /dev/null +++ b/src/data/firestoreClient/services/authorize/AuthorizeService.ts @@ -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} + * @memberof IAuthorizeService + */ + public login: (email: string, password: string) => Promise = (email, password) => { + + return new Promise((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} + * @memberof IAuthorizeService + */ + public logout: () => Promise = () => { + return new Promise((resolve, reject) => { + firebaseAuth() + .signOut() + .then((result) => { + resolve() + }) + .catch((error: any) => { + + reject(new SocialError(error.code, error.message)) + }) + }) + } + + /** + * Register a user + * + * @returns {Promise} + * @memberof IAuthorizeService + */ + public registerUser: (user: User) => Promise = (user) => { + return new Promise((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} + * @memberof IAuthorizeService + */ + public updatePassword: (newPassword: string) => Promise = (newPassword) => { + + return new Promise((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 = (email) => { + return new Promise((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 = () => { + return new Promise((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 = (type) => { + return new Promise((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((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((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))) + }) + } +} diff --git a/src/data/firestoreClient/services/authorize/index.ts b/src/data/firestoreClient/services/authorize/index.ts new file mode 100644 index 0000000..3378979 --- /dev/null +++ b/src/data/firestoreClient/services/authorize/index.ts @@ -0,0 +1,5 @@ +import { AuthorizeService } from './AuthorizeService' + +export { + AuthorizeService +} diff --git a/src/data/firestoreClient/services/circles/CircleService.ts b/src/data/firestoreClient/services/circles/CircleService.ts new file mode 100644 index 0000000..7ce7954 --- /dev/null +++ b/src/data/firestoreClient/services/circles/CircleService.ts @@ -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 = (userId, circle) => { + return new Promise((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 = (userId, circleId, userCircle, userFollower, userFollowingId) => { + return new Promise((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 = (userId, circleId, userFollowingId) => { + return new Promise((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 = (userId, circleId, circle) => { + return new Promise((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 = (userId, circleId) => { + return new Promise((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) + }) + + }) + } +} diff --git a/src/data/firestoreClient/services/circles/index.ts b/src/data/firestoreClient/services/circles/index.ts new file mode 100644 index 0000000..e907c11 --- /dev/null +++ b/src/data/firestoreClient/services/circles/index.ts @@ -0,0 +1,5 @@ +import { CircleService } from './CircleService' + +export { + CircleService +} diff --git a/src/data/firestoreClient/services/comments/CommentService.ts b/src/data/firestoreClient/services/comments/CommentService.ts new file mode 100644 index 0000000..74c05e5 --- /dev/null +++ b/src/data/firestoreClient/services/comments/CommentService.ts @@ -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 = (comment) => { + return new Promise((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 = (comment) => { + return new Promise((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 = (commentId, postId) => { + return new Promise((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)) + }) + }) + } +} diff --git a/src/data/firestoreClient/services/comments/index.ts b/src/data/firestoreClient/services/comments/index.ts new file mode 100644 index 0000000..e4f9a1e --- /dev/null +++ b/src/data/firestoreClient/services/comments/index.ts @@ -0,0 +1,5 @@ +import { CommentService } from './CommentService' + +export { + CommentService +} \ No newline at end of file diff --git a/src/data/firestoreClient/services/common/CommonService.ts b/src/data/firestoreClient/services/common/CommonService.ts new file mode 100644 index 0000000..d15218a --- /dev/null +++ b/src/data/firestoreClient/services/common/CommonService.ts @@ -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 { + +} diff --git a/src/data/firestoreClient/services/common/index.ts b/src/data/firestoreClient/services/common/index.ts new file mode 100644 index 0000000..7470140 --- /dev/null +++ b/src/data/firestoreClient/services/common/index.ts @@ -0,0 +1,5 @@ +import { CommonService } from './CommonService' + +export { + CommonService +} \ No newline at end of file diff --git a/src/data/firestoreClient/services/files/StorageService.ts b/src/data/firestoreClient/services/files/StorageService.ts new file mode 100644 index 0000000..acf15c4 --- /dev/null +++ b/src/data/firestoreClient/services/files/StorageService.ts @@ -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((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) + }) + }) + + } +} diff --git a/src/data/firestoreClient/services/files/index.ts b/src/data/firestoreClient/services/files/index.ts new file mode 100644 index 0000000..10a8449 --- /dev/null +++ b/src/data/firestoreClient/services/files/index.ts @@ -0,0 +1,5 @@ +import { StorageService } from './StorageService' + +export { + StorageService +} diff --git a/src/data/firestoreClient/services/imageGallery/ImageGalleryService.ts b/src/data/firestoreClient/services/imageGallery/ImageGalleryService.ts new file mode 100644 index 0000000..deb3e6b --- /dev/null +++ b/src/data/firestoreClient/services/imageGallery/ImageGalleryService.ts @@ -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 = (userId) => { + return new Promise((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 = (userId, image) => { + return new Promise((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 = (userId, imageId) => { + return new Promise((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 = (image, imageName, progressCallback) => { + return new Promise((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 = (fileName) => { + return new Promise((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)) + }) + }) + } +} diff --git a/src/data/firestoreClient/services/imageGallery/index.ts b/src/data/firestoreClient/services/imageGallery/index.ts new file mode 100644 index 0000000..787b63d --- /dev/null +++ b/src/data/firestoreClient/services/imageGallery/index.ts @@ -0,0 +1,5 @@ +import { ImageGalleryService } from './ImageGalleryService' + +export { + ImageGalleryService +} \ No newline at end of file diff --git a/src/data/firestoreClient/services/index.ts b/src/data/firestoreClient/services/index.ts new file mode 100644 index 0000000..f697b25 --- /dev/null +++ b/src/data/firestoreClient/services/index.ts @@ -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 + +} diff --git a/src/data/firestoreClient/services/notifications/index.ts b/src/data/firestoreClient/services/notifications/index.ts new file mode 100644 index 0000000..943f9ef --- /dev/null +++ b/src/data/firestoreClient/services/notifications/index.ts @@ -0,0 +1,5 @@ +import { NotificationService } from './NotificationService' + +export { + NotificationService +} \ No newline at end of file diff --git a/src/data/firestoreClient/services/notifications/notificationService.ts b/src/data/firestoreClient/services/notifications/notificationService.ts new file mode 100644 index 0000000..28e1172 --- /dev/null +++ b/src/data/firestoreClient/services/notifications/notificationService.ts @@ -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 = (notification: Notification) => { + return new Promise((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((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 = (notificationId, userId, notification) => { + return new Promise((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)) + }) + }) + } + +} diff --git a/src/data/firestoreClient/services/posts/PostService.ts b/src/data/firestoreClient/services/posts/PostService.ts new file mode 100644 index 0000000..0992e1d --- /dev/null +++ b/src/data/firestoreClient/services/posts/PostService.ts @@ -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 = (post) => { + return new Promise((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 = (post) => { + return new Promise((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 = (postId) => { + return new Promise((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 = (postId) => { + return new Promise((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)) + }) + + }) + } +} diff --git a/src/data/firestoreClient/services/posts/index.ts b/src/data/firestoreClient/services/posts/index.ts new file mode 100644 index 0000000..e9c7fd6 --- /dev/null +++ b/src/data/firestoreClient/services/posts/index.ts @@ -0,0 +1,5 @@ +import { PostService } from './PostService' + +export { + PostService +} \ No newline at end of file diff --git a/src/data/firestoreClient/services/users/UserService.ts b/src/data/firestoreClient/services/users/UserService.ts new file mode 100644 index 0000000..db38962 --- /dev/null +++ b/src/data/firestoreClient/services/users/UserService.ts @@ -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 = (userId) => { + return new Promise((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 = (userId, profile) => { + return new Promise((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((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)) + }) + }) + + } + +} diff --git a/src/data/firestoreClient/services/users/index.ts b/src/data/firestoreClient/services/users/index.ts new file mode 100644 index 0000000..d883994 --- /dev/null +++ b/src/data/firestoreClient/services/users/index.ts @@ -0,0 +1,5 @@ +import { UserService } from './UserService' + +export { + UserService +} \ No newline at end of file diff --git a/src/data/firestoreClient/services/votes/VoteService.ts b/src/data/firestoreClient/services/votes/VoteService.ts new file mode 100644 index 0000000..5760995 --- /dev/null +++ b/src/data/firestoreClient/services/votes/VoteService.ts @@ -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 = (vote) => { + return new Promise((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 = (vote) => { + return new Promise((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)) + }) + }) + } +} diff --git a/src/data/firestoreClient/services/votes/index.ts b/src/data/firestoreClient/services/votes/index.ts new file mode 100644 index 0000000..8859aa9 --- /dev/null +++ b/src/data/firestoreClient/services/votes/index.ts @@ -0,0 +1,5 @@ +import { VoteService } from './VoteService' + +export { + VoteService +} diff --git a/src/index.tsx b/src/index.tsx index 4f29fea..3ddface 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -12,9 +12,6 @@ import { Provider } from 'react-redux' import store, { history } from 'store/configureStore' import { ConnectedRouter } from 'react-router-redux' -import 'babel-core/register' -import 'babel-polyfill' - // - Import app components import Master from 'components/master' // import { App } from 'components/AWS' @@ -23,9 +20,10 @@ import Master from 'components/master' // tslint:disable-next-line:no-empty store.subscribe(() => { }) + // Needed for onTouchTap // http://stackoverflow.com/a/34015469/988941 -injectTapEventPlugin() +try { injectTapEventPlugin() } catch (e) { } // This replaces the textColor value on the palette // and then update the keys for each component that depends on it. @@ -37,17 +35,6 @@ const muiTheme = getMuiTheme({ // App css import 'applicationStyles' const supportsHistory = 'pushState' in window.history - -// ReactDOM.render( -// -// -// -// -// -// -// , -// document.getElementById('app') -// ) const render = (Component: any) => { ReactDOM.render( @@ -68,5 +55,5 @@ render(Master) // Webpack Hot Module Replacement API if (module.hot) { - module.hot.accept('components/master', () => { render(Master) }) + module.hot.accept() } \ No newline at end of file diff --git a/src/reducers/authorize/AuthorizeState.ts b/src/reducers/authorize/AuthorizeState.ts index b2f308e..b8e1268 100644 --- a/src/reducers/authorize/AuthorizeState.ts +++ b/src/reducers/authorize/AuthorizeState.ts @@ -28,7 +28,7 @@ export class AuthorizeState { * @type {Boolean} * @memberof AuthorizeState */ - isVerfide: Boolean = false + isVerifide: Boolean = false /** * If user password is updated {true} or not {false} diff --git a/src/reducers/comments/commentReducer.ts b/src/reducers/comments/commentReducer.ts index be7118d..1499378 100644 --- a/src/reducers/comments/commentReducer.ts +++ b/src/reducers/comments/commentReducer.ts @@ -61,10 +61,10 @@ export let commentReducer = (state: CommentState = new CommentState(), action: I } } case CommentActionType.DELETE_COMMENT: - let parsedComments = {} if (!state.postComments![payload.postId]) { return state } + let parsedComments = {} Object.keys(state.postComments![payload.postId]).map((id) => { if (id !== payload.id) { _.merge(parsedComments, { [id]: { ...state.postComments![payload.postId][id] } }) diff --git a/src/routes/HomeRouter.tsx b/src/routes/HomeRouter.tsx new file mode 100644 index 0000000..54de1c1 --- /dev/null +++ b/src/routes/HomeRouter.tsx @@ -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 { + render () { + const { enabled, match, data } = this.props + return ( + enabled ? ( + } /> + + + )} /> + + + + )} /> + ) + : '' + + ) + } +} +export default withRouter(connect(null, null)(HomeRouter as any)) diff --git a/src/routes/IRoute.ts b/src/routes/IRoute.ts new file mode 100644 index 0000000..17dade6 --- /dev/null +++ b/src/routes/IRoute.ts @@ -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 + +} diff --git a/src/routes/IRouterProps.ts b/src/routes/IRouterProps.ts new file mode 100644 index 0000000..92379c9 --- /dev/null +++ b/src/routes/IRouterProps.ts @@ -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 + +} diff --git a/src/routes/MasterRouter.tsx b/src/routes/MasterRouter.tsx new file mode 100644 index 0000000..5bc5051 --- /dev/null +++ b/src/routes/MasterRouter.tsx @@ -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 { + render () { + const { enabled, match, data } = this.props + return ( + enabled ? ( + + + + + } /> + } /> + ) + : '' + + ) + } +} +export default withRouter(connect(null, null)(MasterRouter as any)) diff --git a/src/routes/PrivateRoute.tsx b/src/routes/PrivateRoute.tsx new file mode 100644 index 0000000..794a2a8 --- /dev/null +++ b/src/routes/PrivateRoute.tsx @@ -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 { + + render () { + const {authed, path, component} = this.props + return ( + { + return ( + authed + ? (() => component)() + : + ) + }} /> + ) + } +} + +const mapStateToProps = (state: any, nexProps: IRoute) => { + const { authorize } = state + return { + authed: authorize.authed + } +} + +export default connect(mapStateToProps)(PrivateRoute as any) diff --git a/src/routes/PublicRoute.tsx b/src/routes/PublicRoute.tsx new file mode 100644 index 0000000..134f7c7 --- /dev/null +++ b/src/routes/PublicRoute.tsx @@ -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 { + + render () { + const {authed, path, component} = this.props + return ( + { + return ( + authed + ? + : (() => component)() + ) + }} /> + ) + } +} + +const mapStateToProps = (state: any, nexProps: IRoute) => { + const { authorize } = state + return { + authed: authorize.authed + } +} + +export default connect(mapStateToProps)(PublicRoute as any) diff --git a/src/routes/index.ts b/src/routes/index.ts new file mode 100644 index 0000000..5072185 --- /dev/null +++ b/src/routes/index.ts @@ -0,0 +1,7 @@ +import MasterRouter from './MasterRouter' +import HomeRouter from './HomeRouter' + +export { + MasterRouter, + HomeRouter +} diff --git a/tsconfig.json b/tsconfig.json index e0b215e..7475e3e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,36 +1,37 @@ { "compilerOptions": { - "module": "es6", - "target": "es6", - "lib": ["es6","dom"], - "types": ["reflect-metadata"], - "allowSyntheticDefaultImports": true, // see below - "baseUrl": "./src/", // enables you to import relative to this folder - "paths": { - "src/*" : ["*"], - "core/*" : ["core/*"], - "data/*" : ["data/*"], - "components/*" : ["components/*"], - "store/*" : ["store/*"], - "api/*" : ["api/*"], - "layouts/*" : ["layouts/*"], - "models/*" : ["models/*"] + "module": "es6", + "target": "es6", + "lib": ["es6", "dom"], + "types": ["reflect-metadata"], + "allowSyntheticDefaultImports": true, // see below + "baseUrl": "./src/", // enables you to import relative to this folder + "paths": { + "src/*": ["*"], + "core/*": ["core/*"], + "routes/*": ["routes/*"], + "data/*": ["data/*"], + "components/*": ["components/*"], + "store/*": ["store/*"], + "api/*": ["api/*"], + "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 - "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 - - }, - "include":[ - "src/**/*" - ], - "exclude": [ - "node_modules" - ] + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules" + ] } \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index cb95361..1402efe 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,6 +1,7 @@ var webpack = require('webpack'); var path = require('path'); var envFile = require('node-env-file'); +var OpenBrowserPlugin = require('open-browser-webpack-plugin'); process.env.NODE_ENV = process.env.NODE_ENV || 'development'; @@ -41,6 +42,7 @@ module.exports = { }) ] : [ new webpack.HotModuleReplacementPlugin(), + new OpenBrowserPlugin({ url: `http://localhost:${process.env.PORT}` }), new webpack.DefinePlugin({ 'process.env': { NODE_ENV: JSON.stringify(process.env.NODE_ENV), @@ -50,14 +52,15 @@ module.exports = { STORAGE_BUCKET: JSON.stringify(process.env.STORAGE_BUCKET), PROJECT_ID: JSON.stringify(process.env.PROJECT_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: { publicPath: '/', path: path.resolve(__dirname, './public'), - filename: 'bundle-v0.1.js', + filename: 'bundle-v0.3.js', }, resolve: { @@ -75,6 +78,7 @@ module.exports = { data: 'src/data', api: 'src/api', layouts: 'src/layouts', + routes: 'src/routes', models: 'src/models', store: 'src/store', applicationStyles: 'src/styles/app.scss',