Migrate components to typescript
This commit is contained in:
147
src/actions/authorizeActions.ts
Normal file
147
src/actions/authorizeActions.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
|
||||
// - Import react components
|
||||
import moment from 'moment'
|
||||
import { push } from 'react-router-redux'
|
||||
|
||||
// -Import domain
|
||||
import { User } from 'core/domain/users'
|
||||
import { SocialError } from 'core/domain/common'
|
||||
|
||||
import { UserRegisterModel } from 'models/users/userRegisterModel'
|
||||
|
||||
// - Import action types
|
||||
import { AuthorizeActionType } from 'constants/authorizeActionType'
|
||||
|
||||
// - Import services
|
||||
import { IAuthorizeService } from 'core/services/authorize'
|
||||
import { IServiceProvider, ServiceProvide } from 'core/factories'
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const authorizeService: IAuthorizeService = serviceProvider.createAuthorizeService()
|
||||
|
||||
/* _____________ CRUD DB _____________ */
|
||||
|
||||
/**
|
||||
* Log in user in server
|
||||
* @param {string} email
|
||||
* @param {string} password
|
||||
*/
|
||||
export const dbLogin = (email: string, password: string) => {
|
||||
return (dispatch: any, getState: any) => {
|
||||
dispatch(globalActions.showNotificationRequest())
|
||||
return authorizeService.login(email, password).then((result) => {
|
||||
dispatch(globalActions.showNotificationSuccess())
|
||||
dispatch(login(result.uid))
|
||||
dispatch(push('/'))
|
||||
}, (error: SocialError) => dispatch(globalActions.showErrorMessage(error.code)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log out user in server
|
||||
*/
|
||||
export const dbLogout = () => {
|
||||
return (dispatch: any, getState: any) => {
|
||||
return authorizeService.logout().then((result) => {
|
||||
dispatch(logout())
|
||||
dispatch(push('/login'))
|
||||
|
||||
}, (error: SocialError) => dispatch(globalActions.showErrorMessage(error.code)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param user for registering
|
||||
*/
|
||||
export const dbSignup = (user: UserRegisterModel) => {
|
||||
return (dispatch: Function, getState: Function) => {
|
||||
dispatch(globalActions.showNotificationRequest())
|
||||
let newUser = new User()
|
||||
newUser.email = user.email
|
||||
newUser.password = user.password
|
||||
newUser.fullName = user.fullName
|
||||
|
||||
return authorizeService.registerUser(newUser).then((result) => {
|
||||
dispatch(signup({
|
||||
userId: result.uid,
|
||||
...user
|
||||
}))
|
||||
dispatch(push('/'))
|
||||
})
|
||||
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.code)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Change user's password
|
||||
* @param {string} newPassword
|
||||
*/
|
||||
export const dbUpdatePassword = (newPassword: string) => {
|
||||
return (dispatch: any, getState: any) => {
|
||||
dispatch(globalActions.showNotificationRequest())
|
||||
|
||||
return authorizeService.updatePassword(newPassword).then(() => {
|
||||
|
||||
// Update successful.
|
||||
dispatch(globalActions.showNotificationSuccess())
|
||||
dispatch(updatePassword())
|
||||
dispatch(push('/'))
|
||||
})
|
||||
.catch((error: SocialError) => {
|
||||
// An error happened.
|
||||
switch (error.code) {
|
||||
case 'auth/requires-recent-login':
|
||||
dispatch(globalActions.showErrorMessage(error.code))
|
||||
dispatch(dbLogout())
|
||||
break
|
||||
default:
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/* _____________ CRUD State _____________ */
|
||||
|
||||
/**
|
||||
* Loing user
|
||||
* @param {string} uids
|
||||
*/
|
||||
export const login = (uid: string) => {
|
||||
return {
|
||||
type: AuthorizeActionType.LOGIN,
|
||||
payload: { authed: true, uid }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout user
|
||||
*/
|
||||
export const logout = () => {
|
||||
return { type: AuthorizeActionType.LOGOUT }
|
||||
}
|
||||
|
||||
/**
|
||||
* User registeration call
|
||||
* @param user for registering
|
||||
*/
|
||||
export const signup = (user: UserRegisterModel) => {
|
||||
return {
|
||||
type: AuthorizeActionType.SIGNUP,
|
||||
payload: { ...user }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user's password
|
||||
*/
|
||||
export const updatePassword = () => {
|
||||
return { type: AuthorizeActionType.UPDATE_PASSWORD }
|
||||
}
|
||||
316
src/actions/circleActions.ts
Normal file
316
src/actions/circleActions.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
// - Import domain
|
||||
import { User } from 'core/domain/users'
|
||||
import { Circle, UserFollower } from 'core/domain/circles'
|
||||
import { SocialError } from 'core/domain/common'
|
||||
|
||||
// - Import utility components
|
||||
import moment from 'moment'
|
||||
|
||||
// - Import action types
|
||||
import { CircleActionType } from 'constants/circleActionType'
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as postActions from 'actions/postActions'
|
||||
import * as userActions from 'actions/userActions'
|
||||
import * as notifyActions from 'actions/notifyActions'
|
||||
|
||||
import { IServiceProvider,ServiceProvide } from 'core/factories'
|
||||
import { ICircleService } from 'core/services/circles'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const circleService: ICircleService = serviceProvider.createCircleService()
|
||||
|
||||
/* _____________ CRUD DB _____________ */
|
||||
|
||||
/**
|
||||
* Add a circle
|
||||
* @param {string} circleName
|
||||
*/
|
||||
export let dbAddCircle = (circleName: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
let uid: string = getState().authorize.uid
|
||||
let circle: Circle = {
|
||||
creationDate: moment().unix(),
|
||||
name: circleName,
|
||||
users: {}
|
||||
}
|
||||
return circleService.addCircle(uid,circle).then((circleKey: string) => {
|
||||
circle.id = circleKey
|
||||
circle.ownerId = uid
|
||||
dispatch(addCircle(circle))
|
||||
|
||||
}, (error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a user in a circle
|
||||
* @param {string} cid is circle identifier
|
||||
* @param {User} userFollowing is the user for following
|
||||
*/
|
||||
export let dbAddFollowingUser = (cid: string, userFollowing: UserFollower) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
let uid: string = getState().authorize.uid
|
||||
let user: User = getState().user.info[uid]
|
||||
|
||||
let userCircle: User = {
|
||||
creationDate: moment().unix(),
|
||||
fullName: userFollowing.fullName,
|
||||
avatar: userFollowing.avatar || ''
|
||||
}
|
||||
let userFollower: UserFollower = {
|
||||
creationDate: moment().unix(),
|
||||
fullName: user.fullName,
|
||||
avatar: user.avatar || '',
|
||||
approved: false
|
||||
}
|
||||
|
||||
return circleService.addFollowingUser(uid,cid,userCircle,userFollower,userFollowing.userId as string)
|
||||
.then(() => {
|
||||
dispatch(addFollowingUser(uid, cid, userFollowing.userId as string, { ...userCircle } as User))
|
||||
|
||||
dispatch(notifyActions.dbAddNotification(
|
||||
{
|
||||
description: `${user.fullName} follow you.`,
|
||||
url: `/${uid}`,
|
||||
notifyRecieverUserId: userFollowing.userId as string,
|
||||
notifierUserId: uid,
|
||||
isSeen: false
|
||||
}))
|
||||
|
||||
}, (error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a user from a circle
|
||||
* @param {string} cid is circle identifier
|
||||
* @param {string} userFollowingId following user identifier
|
||||
*/
|
||||
export let dbDeleteFollowingUser = (cid: string, userFollowingId: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
let uid: string = getState().authorize.uid
|
||||
|
||||
return circleService.deleteFollowingUser(uid,cid,userFollowingId)
|
||||
.then(() => {
|
||||
dispatch(deleteFollowingUser(uid, cid, userFollowingId))
|
||||
}, (error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a circle from database
|
||||
* @param {Circle} newCircle
|
||||
*/
|
||||
export const dbUpdateCircle = (newCircle: Circle) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
// Get current user id
|
||||
let uid: string = getState().authorize.uid
|
||||
|
||||
// Write the new data simultaneously in the list
|
||||
let circle: Circle = getState().circle.userCircles[uid][newCircle.id!]
|
||||
let updatedCircle: Circle = {
|
||||
name: newCircle.name || circle.name,
|
||||
users: newCircle.users ? newCircle.users : (circle.users || [])
|
||||
}
|
||||
return circleService.updateCircle(uid,newCircle.id!,circle)
|
||||
.then(() => {
|
||||
dispatch(updateCircle(uid,{ id: newCircle.id, ...updatedCircle }))
|
||||
}, (error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a circle from database
|
||||
* @param {string} id is circle identifier
|
||||
*/
|
||||
export const dbDeleteCircle = (id: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
// Get current user id
|
||||
let uid: string = getState().authorize.uid
|
||||
|
||||
return circleService.deleteCircle(uid,id)
|
||||
.then(() => {
|
||||
dispatch(deleteCircle(uid, id))
|
||||
}, (error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all user circles from data base
|
||||
*/
|
||||
export const dbGetCircles = () => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
let uid: string = getState().authorize.uid
|
||||
if (uid) {
|
||||
|
||||
return circleService.getCircles(uid)
|
||||
.then((circles: { [circleId: string]: Circle }) => {
|
||||
Object.keys(circles).forEach((circleId) => {
|
||||
if (circleId !== '-Followers' && circles[circleId].users) {
|
||||
Object.keys(circles[circleId].users).filter((v, i, a) => a.indexOf(v) === i).forEach((userId) => {
|
||||
dispatch(postActions.dbGetPostsByUserId(userId))
|
||||
dispatch(userActions.dbGetUserInfoByUserId(userId, ''))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
dispatch(addCircles(uid, circles))
|
||||
})
|
||||
.catch((error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all user circles from data base by user id
|
||||
* @param uid user identifier
|
||||
*/
|
||||
export const dbGetCirclesByUserId = (uid: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
if (uid) {
|
||||
return circleService.getCircles(uid)
|
||||
.then((circles: { [circleId: string]: Circle }) => {
|
||||
dispatch(addCircles(uid, circles))
|
||||
})
|
||||
.catch((error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* _____________ CRUD State _____________ */
|
||||
|
||||
/**
|
||||
* Add a normal circle
|
||||
* @param {string} uid is user identifier
|
||||
* @param {Circle} circle
|
||||
*/
|
||||
export const addCircle = (circle: Circle) => {
|
||||
return {
|
||||
type: CircleActionType.ADD_CIRCLE,
|
||||
payload: { circle }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a circle
|
||||
* @param {string} uid is user identifier
|
||||
* @param {Circle} circle
|
||||
*/
|
||||
export const updateCircle = (uid: string, circle: Circle) => {
|
||||
return {
|
||||
type: CircleActionType.UPDATE_CIRCLE,
|
||||
payload: { uid, circle }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a circle
|
||||
* @param {string} uid is user identifier
|
||||
* @param {string} id is circle identifier
|
||||
*/
|
||||
export const deleteCircle = (uid: string, id: string) => {
|
||||
return {
|
||||
type: CircleActionType.DELETE_CIRCLE,
|
||||
payload: { uid, id }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a list of circle
|
||||
* @param {string} uid
|
||||
* @param {circleId: string]: Circle} circles
|
||||
*/
|
||||
export const addCircles = (uid: string, circles: { [circleId: string]: Circle }) => {
|
||||
return {
|
||||
type: CircleActionType.ADD_LIST_CIRCLE,
|
||||
payload: { uid, circles }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clea all data in circle store
|
||||
*/
|
||||
export const clearAllCircles = () => {
|
||||
return {
|
||||
type: CircleActionType.CLEAR_ALL_CIRCLES
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open circle settings
|
||||
* @param uid user idenifier
|
||||
* @param id circle identifier
|
||||
*/
|
||||
export const openCircleSettings = (uid: string, id: string) => {
|
||||
return {
|
||||
type: CircleActionType.OPEN_CIRCLE_SETTINGS,
|
||||
payload: { uid, id }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Close open circle settings
|
||||
* @param uid user identifier
|
||||
* @param id circle identifier
|
||||
*/
|
||||
export const closeCircleSettings = (uid: string, id: string) => {
|
||||
return {
|
||||
type: CircleActionType.CLOSE_CIRCLE_SETTINGS,
|
||||
payload: { uid, id }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add following user in a circle
|
||||
* @param {string} uid user identifire who want to follow the following user
|
||||
* @param {string} cid circle identifier that following user should be added in
|
||||
* @param {string} followingId following user identifier
|
||||
* @param {User} userCircle information about following user
|
||||
*/
|
||||
export const addFollowingUser = (uid: string, cid: string, followingId: string, userCircle: User) => {
|
||||
return {
|
||||
type: CircleActionType.ADD_FOLLOWING_USER,
|
||||
payload: { uid, cid, followingId, userCircle }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete following user from a circle
|
||||
* @param {string} uid user identifire who want to follow the following user
|
||||
* @param {string} cid circle identifier that following user should be added in
|
||||
* @param {string} followingId following user identifier
|
||||
*/
|
||||
export const deleteFollowingUser = (uid: string, cid: string, followingId: string) => {
|
||||
return {
|
||||
type: CircleActionType.DELETE_FOLLOWING_USER,
|
||||
payload: { uid, cid, followingId }
|
||||
}
|
||||
|
||||
}
|
||||
227
src/actions/commentActions.ts
Normal file
227
src/actions/commentActions.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
// - Import react components
|
||||
import moment from 'moment'
|
||||
|
||||
// - Import domain
|
||||
import { Comment } from 'core/domain/comments'
|
||||
import { SocialError } from 'core/domain/common'
|
||||
|
||||
// - Import action types
|
||||
import { CommentActionType } from 'constants/commentActionType'
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as notifyActions from 'actions/notifyActions'
|
||||
|
||||
import { IServiceProvider, ServiceProvide } from 'core/factories'
|
||||
import { ICommentService } from 'core/services/comments'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const commentService: ICommentService = serviceProvider.createCommentService()
|
||||
|
||||
/* _____________ CRUD DB _____________ */
|
||||
|
||||
/**
|
||||
* Add comment to database
|
||||
* @param {object} ownerPostUserId the identifier of the user who is the owner of the post which comment belong to
|
||||
* @param {object} newComment user comment
|
||||
* @param {function} callBack will be fired when server responsed
|
||||
*/
|
||||
export const dbAddComment = (ownerPostUserId: string | null,newComment: Comment, callBack: Function) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
dispatch(globalActions.showTopLoading())
|
||||
|
||||
let uid: string = getState().authorize.uid
|
||||
|
||||
let comment: Comment = {
|
||||
score : 0,
|
||||
creationDate : moment().unix(),
|
||||
userDisplayName : getState().user.info[uid].fullName,
|
||||
userAvatar : getState().user.info[uid].avatar,
|
||||
userId : uid,
|
||||
postId: newComment.postId,
|
||||
text: newComment.text
|
||||
}
|
||||
|
||||
return commentService.addComment(newComment.postId,comment)
|
||||
.then((commentKey: string) => {
|
||||
dispatch(addComment({id: commentKey! ,...comment}))
|
||||
callBack()
|
||||
dispatch(globalActions.hideTopLoading())
|
||||
|
||||
if (ownerPostUserId && ownerPostUserId !== uid) {
|
||||
dispatch(notifyActions.dbAddNotification(
|
||||
{
|
||||
description: 'Add comment on your post.',
|
||||
url: `/${ownerPostUserId}/posts/${comment.postId}`,
|
||||
notifyRecieverUserId: ownerPostUserId, notifierUserId: uid,
|
||||
isSeen: false
|
||||
}))
|
||||
}
|
||||
|
||||
}, (error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
dispatch(globalActions.hideTopLoading())
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all comments from database
|
||||
*/
|
||||
export const dbGetComments = () => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
let uid: string = getState().authorize.uid
|
||||
if (uid) {
|
||||
return commentService.getComments((comments: {[postId: string]: {[commentId: string]: Comment}}) => {
|
||||
dispatch(addCommentList(comments))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a comment from database
|
||||
* @param {string} id of comment
|
||||
* @param {string} postId is the identifier of the post which comment belong to
|
||||
* @param {string} text is the text of comment
|
||||
*/
|
||||
export const dbUpdateComment = (id: string, postId: string, text: string) => {
|
||||
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 comment: Comment = getState().comment.postComments[postId][id]
|
||||
let updatedComment: Comment = {
|
||||
postId: postId,
|
||||
score: comment.score,
|
||||
text: text,
|
||||
creationDate: comment.creationDate,
|
||||
userDisplayName: comment.userDisplayName,
|
||||
userAvatar: comment.userAvatar,
|
||||
userId: uid
|
||||
}
|
||||
|
||||
return commentService.updateComment(uid,postId,updatedComment)
|
||||
.then(() => {
|
||||
dispatch(updateComment( id, postId, text))
|
||||
dispatch(globalActions.hideTopLoading())
|
||||
|
||||
}, (error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
dispatch(globalActions.hideTopLoading())
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a comment from database
|
||||
* @param {string} id of comment
|
||||
* @param {string} postId is the identifier of the post which comment belong to
|
||||
*/
|
||||
export const dbDeleteComment = (id?: string | null, postId?: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
if (id === undefined || id === null) {
|
||||
dispatch(globalActions.showErrorMessage('comment id can not be null or undefined'))
|
||||
}
|
||||
dispatch(globalActions.showTopLoading())
|
||||
|
||||
// Get current user id
|
||||
let uid: string = getState().authorize.uid
|
||||
|
||||
return commentService.deleteComment(id!,postId!)
|
||||
.then(() => {
|
||||
dispatch(deleteComment(id!, postId!))
|
||||
dispatch(globalActions.hideTopLoading())
|
||||
|
||||
}, (error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
dispatch(globalActions.hideTopLoading())
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* _____________ CRUD State _____________ */
|
||||
|
||||
/**
|
||||
* Add comment
|
||||
* @param {Comment} data
|
||||
*/
|
||||
export const addComment = (comment: Comment) => {
|
||||
|
||||
return {
|
||||
type: CommentActionType.ADD_COMMENT,
|
||||
payload: comment
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param id comment identifier
|
||||
* @param postId post identefier which comment belong to
|
||||
* @param text the new text for comment
|
||||
*/
|
||||
export const updateComment = ( id: string, postId: string, text: string) => {
|
||||
|
||||
return {
|
||||
type: CommentActionType.UPDATE_COMMENT,
|
||||
payload: {id, postId, text}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add comment list
|
||||
* @param {[postId: string]: {[commentId: string] : Comment}} postComments an array of comments
|
||||
*/
|
||||
export const addCommentList = (postComments: {[postId: string]: {[commentId: string]: Comment}}) => {
|
||||
|
||||
return {
|
||||
type: CommentActionType.ADD_COMMENT_LIST,
|
||||
payload: postComments
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a comment
|
||||
* @param {string} id of comment
|
||||
* @param {string} postId is the identifier of the post which comment belong to
|
||||
*/
|
||||
export const deleteComment = (id: string, postId: string) => {
|
||||
return { type: CommentActionType.DELETE_COMMENT, payload: { id, postId } }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all data
|
||||
*/
|
||||
export const clearAllData = () => {
|
||||
return {
|
||||
type: CommentActionType.CLEAR_ALL_DATA_COMMENT
|
||||
}
|
||||
}
|
||||
|
||||
export const openCommentEditor = (comment: Comment) => {
|
||||
|
||||
return {
|
||||
type: CommentActionType.OPEN_COMMENT_EDITOR,
|
||||
payload: comment
|
||||
}
|
||||
}
|
||||
|
||||
export const closeCommentEditor = (comment: Comment) => {
|
||||
|
||||
return {
|
||||
type: CommentActionType.CLOSE_COMMENT_EDITOR,
|
||||
payload: comment
|
||||
}
|
||||
}
|
||||
173
src/actions/globalActions.ts
Normal file
173
src/actions/globalActions.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
// - Import image gallery action types
|
||||
import { GlobalActionType } from 'constants/globalActionType'
|
||||
|
||||
// - Import actions
|
||||
import * as postActions from 'actions/postActions'
|
||||
import * as commentActions from 'actions/commentActions'
|
||||
import * as userActions from 'actions/userActions'
|
||||
|
||||
/**
|
||||
* Progress change
|
||||
* @param {string} percent
|
||||
* @param {boolean} visible
|
||||
*/
|
||||
export const progressChange = (percent: number, visible: Boolean) => {
|
||||
return {
|
||||
type: GlobalActionType.PROGRESS_CHANGE,
|
||||
payload: {percent, visible}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Default data loaded status will be true
|
||||
*/
|
||||
export const defaultDataEnable = () => {
|
||||
return{
|
||||
type: GlobalActionType.DEFAULT_DATA_ENABLE
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Default data loaded status will be false
|
||||
* @param {boolean} status
|
||||
*/
|
||||
export const defaultDataDisable = () => {
|
||||
return{
|
||||
type: GlobalActionType.DEFAULT_DATA_DISABLE
|
||||
}
|
||||
}
|
||||
|
||||
// - Show notification of request
|
||||
export const showNotificationRequest = () => {
|
||||
return{
|
||||
type: GlobalActionType.SHOW_SEND_REQUEST_MESSAGE_GLOBAL
|
||||
}
|
||||
}
|
||||
|
||||
// - Show notification of success
|
||||
export const showNotificationSuccess = () => {
|
||||
return{
|
||||
type: GlobalActionType.SHOW_REQUEST_SUCCESS_MESSAGE_GLOBAL
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide global message
|
||||
*/
|
||||
export const hideMessage = () => {
|
||||
return{
|
||||
type: GlobalActionType.HIDE_MESSAGE_GLOBAL
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Show error message
|
||||
* @param {string} message
|
||||
*/
|
||||
export const showErrorMessage = (message: string) => {
|
||||
return {
|
||||
type: GlobalActionType.SHOW_ERROR_MESSAGE_GLOBAL,
|
||||
payload: message
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set header title
|
||||
*/
|
||||
export const setHeaderTitleOpt = (callerKey: string,payload: any) => {
|
||||
return (dispatch: any,getState: Function) => {
|
||||
switch (callerKey) {
|
||||
case 'profile':
|
||||
const userName = getState().user.info && getState().user.info[payload] ? getState().user.info[payload].fullName : ''
|
||||
dispatch(setHeaderTitle(userName))
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set header title
|
||||
*/
|
||||
export const setHeaderTitle = (text: string) => {
|
||||
return{
|
||||
type: GlobalActionType.SET_HEADER_TITLE,
|
||||
payload: text
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Open post write
|
||||
*/
|
||||
export const openPostWrite = () => {
|
||||
return{
|
||||
type: GlobalActionType.OPEN_POST_WRITE
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Close post write
|
||||
*/
|
||||
export const closePostWrite = () => {
|
||||
return{
|
||||
type: GlobalActionType.CLOSE_POST_WRITE
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Show top loading
|
||||
*/
|
||||
export const showTopLoading = () => {
|
||||
return{
|
||||
type: GlobalActionType.SHOW_TOP_LOADING
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide top loading
|
||||
*/
|
||||
export const hideTopLoading = () => {
|
||||
return{
|
||||
type: GlobalActionType.HIDE_TOP_LOADING
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Store temp data
|
||||
*/
|
||||
export const temp = (data: any) => {
|
||||
return{
|
||||
type: GlobalActionType.TEMP,
|
||||
payload: data
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear temp data
|
||||
*/
|
||||
export const clearTemp = () => {
|
||||
return{
|
||||
type: GlobalActionType.CLEAR_TEMP
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// - Load data for guest
|
||||
export const loadDataGuest = () => {
|
||||
// tslint:disable-next-line:no-empty
|
||||
return (dispatch: any,getState: Function) => {
|
||||
}
|
||||
|
||||
}
|
||||
211
src/actions/imageGalleryActions.ts
Normal file
211
src/actions/imageGalleryActions.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
// - Import react componetns
|
||||
import moment from 'moment'
|
||||
|
||||
// - Import domain
|
||||
import { Image } from 'core/domain/imageGallery'
|
||||
import { SocialError } from 'core/domain/common'
|
||||
|
||||
// - Import action types
|
||||
import { ImageGalleryActionType } from 'constants/imageGalleryActionType'
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
|
||||
// - Import app API
|
||||
import FileAPI from 'api/FileAPI'
|
||||
|
||||
import { IServiceProvider, ServiceProvide } from 'core/factories'
|
||||
import { IImageGalleryService } from 'core/services/imageGallery'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const imageGalleryService: IImageGalleryService = serviceProvider.createImageGalleryService()
|
||||
|
||||
/* _____________ UI Actions _____________ */
|
||||
|
||||
/**
|
||||
* Download images for image gallery
|
||||
*/
|
||||
export const dbGetImageGallery = () => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
let uid: string = getState().authorize.uid
|
||||
if (uid) {
|
||||
|
||||
return imageGalleryService.getImageGallery(uid)
|
||||
.then((images: Image[]) => {
|
||||
dispatch(addImageList(images))
|
||||
})
|
||||
.catch((error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* _____________ CRUD Database_____________ */
|
||||
|
||||
/**
|
||||
* Save image URL in the server
|
||||
* @param {string} imageURL is the URL of image
|
||||
* @param {string} imageFullPath is the folder name + / + file name
|
||||
*/
|
||||
export const dbSaveImage = (imageURL: string,imageFullPath: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
let uid: string = getState().authorize.uid
|
||||
let image: Image = {
|
||||
creationDate: moment().unix(),
|
||||
deleteDate: '',
|
||||
URL: imageURL,
|
||||
fullPath: imageFullPath,
|
||||
ownerUserId: uid,
|
||||
lastEditDate: 0,
|
||||
deleted: false
|
||||
}
|
||||
return imageGalleryService.saveImage(uid,image)
|
||||
.then((imageKey: string) => {
|
||||
dispatch(addImage({
|
||||
...image,
|
||||
id: imageKey
|
||||
}))
|
||||
})
|
||||
.catch((error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an image from database
|
||||
* @param {string} id of image
|
||||
*/
|
||||
export const dbDeleteImage = (id: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
// Get current user id
|
||||
let uid: string = getState().authorize.uid
|
||||
|
||||
return imageGalleryService.deleteImage(uid,id)
|
||||
.then(() => {
|
||||
dispatch(deleteImage(id))
|
||||
})
|
||||
.catch((error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload image on the server
|
||||
* @param {file} file
|
||||
* @param {string} fileName
|
||||
*/
|
||||
export const dbUploadImage = (file: any, fileName: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
return imageGalleryService
|
||||
.uploadImage(file,fileName, (percentage: number) => {
|
||||
dispatch(globalActions.progressChange(percentage, true))
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(globalActions.progressChange(100, false))
|
||||
dispatch(dbSaveImage(fileName,''))
|
||||
dispatch(globalActions.hideTopLoading())
|
||||
})
|
||||
.catch((error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.code))
|
||||
dispatch(globalActions.hideTopLoading())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download image from server
|
||||
* @param {string} fileName
|
||||
*/
|
||||
export const dbDownloadImage = (fileName: string) => {
|
||||
|
||||
return (dispatch: any, getState: Function) => {
|
||||
if (fileName === 'noImage') {
|
||||
return {}
|
||||
}
|
||||
if (getState().imageGallery.imageURLList[fileName] && fileName !== '') {
|
||||
return
|
||||
}
|
||||
if (getState().imageGallery.imageRequests.indexOf(fileName) > -1) {
|
||||
return
|
||||
}
|
||||
dispatch(sendImageRequest(fileName))
|
||||
|
||||
return imageGalleryService.downloadImage(fileName)
|
||||
.then((url: string) => {
|
||||
// Insert url into an <img> tag to 'download'
|
||||
if (!getState().imageGallery.imageURLList[fileName] || fileName === '') {
|
||||
dispatch(setImageURL(fileName, url))
|
||||
}
|
||||
})
|
||||
.catch((error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/* _____________ CRUD State _____________ */
|
||||
|
||||
/**
|
||||
* Add image list to image gallery
|
||||
* @param {Image[]} images is an array of images
|
||||
*/
|
||||
export const addImageList = (images: Image[]) => {
|
||||
return { type: ImageGalleryActionType.ADD_IMAGE_LIST_GALLERY,payload: images }
|
||||
}
|
||||
|
||||
/**
|
||||
* Add image to image gallery
|
||||
* @param {Image} image
|
||||
*/
|
||||
export const addImage = (image: Image) => {
|
||||
return { type: ImageGalleryActionType.ADD_IMAGE_GALLERY, payload: image }
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an image
|
||||
* @param {string} id is an image identifier
|
||||
*/
|
||||
export const deleteImage = (id: string) => {
|
||||
return { type: ImageGalleryActionType.DELETE_IMAGE, payload: id }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an image
|
||||
*/
|
||||
export const setImageURL = (name: string, url: string) => {
|
||||
return {
|
||||
type: ImageGalleryActionType.SET_IMAGE_URL,
|
||||
payload: { name, url }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all data in image gallery store
|
||||
*/
|
||||
export const clearAllData = () => {
|
||||
return {
|
||||
type: ImageGalleryActionType.CLEAT_ALL_DATA_IMAGE_GALLERY
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send image request
|
||||
*/
|
||||
export const sendImageRequest = (name: string) => {
|
||||
return {
|
||||
type: ImageGalleryActionType.SEND_IMAGE_REQUEST,
|
||||
payload: name
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
21
src/actions/index.ts
Normal file
21
src/actions/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import * as authorizeActions from './authorizeActions'
|
||||
import * as circleActions from './circleActions'
|
||||
import * as commentActions from './commentActions'
|
||||
import * as globalActions from './globalActions'
|
||||
import * as imageGalleryActions from './imageGalleryActions'
|
||||
import * as notifyActions from './notifyActions'
|
||||
import * as postActions from './postActions'
|
||||
import * as userActions from './userActions'
|
||||
import * as voteActions from './voteActions'
|
||||
|
||||
export {
|
||||
authorizeActions,
|
||||
circleActions,
|
||||
commentActions,
|
||||
globalActions,
|
||||
imageGalleryActions,
|
||||
notifyActions,
|
||||
postActions,
|
||||
userActions,
|
||||
voteActions
|
||||
}
|
||||
164
src/actions/notifyActions.ts
Normal file
164
src/actions/notifyActions.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
// - Import react components
|
||||
import moment from 'moment'
|
||||
|
||||
// - Import domain
|
||||
import { Notification } from 'core/domain/notifications'
|
||||
import { SocialError } from 'core/domain/common'
|
||||
|
||||
// - Import action types
|
||||
import { NotificationActionType } from 'constants/notificationActionType'
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as userActions from 'actions/userActions'
|
||||
|
||||
import { IServiceProvider, ServiceProvide } from 'core/factories'
|
||||
import { INotificationService } from 'core/services/notifications'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const notificationService: INotificationService = serviceProvider.createNotificationService()
|
||||
|
||||
/* _____________ CRUD DB _____________ */
|
||||
|
||||
/**
|
||||
* Add notificaition to database
|
||||
* @param {object} newNotify user notificaition
|
||||
*/
|
||||
export const dbAddNotification = (newNotify: Notification) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
let uid: string = getState().authorize.uid
|
||||
|
||||
let notify: Notification = {
|
||||
isSeen: false,
|
||||
description: newNotify.description,
|
||||
url: newNotify.url,
|
||||
notifierUserId: newNotify.notifierUserId,
|
||||
notifyRecieverUserId: newNotify.notifyRecieverUserId
|
||||
}
|
||||
|
||||
return notificationService.addNotification(notify)
|
||||
.then(() => {
|
||||
dispatch(addNotify())
|
||||
})
|
||||
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all notificaitions from database
|
||||
*/
|
||||
export const dbGetNotifications = () => {
|
||||
return (dispatch: Function , getState: Function) => {
|
||||
let uid: string = getState().authorize.uid
|
||||
if (uid) {
|
||||
return notificationService.getNotifications(uid,
|
||||
(notifications: { [notifyId: string]: Notification} ) => {
|
||||
Object.keys(notifications).forEach((key => {
|
||||
if (!getState().user.info[notifications[key].notifierUserId]) {
|
||||
dispatch(userActions.dbGetUserInfoByUserId(notifications[key].notifierUserId,''))
|
||||
}
|
||||
}))
|
||||
dispatch(addNotifyList(notifications))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a notificaition from database
|
||||
* @param {string} id of notificaition
|
||||
*/
|
||||
export const dbDeleteNotification = (id: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
// Get current user id
|
||||
let uid: string = getState().authorize.uid
|
||||
|
||||
return notificationService.deleteNotification(id,uid).then(() => {
|
||||
dispatch(deleteNotify(id))
|
||||
})
|
||||
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Make seen a notificaition from database
|
||||
* @param {string} id of notificaition
|
||||
*/
|
||||
export const dbSeenNotification = (id: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
// Get current user id
|
||||
let uid: string = getState().authorize.uid
|
||||
let notify: Notification = getState().notify.userNotifies[id]
|
||||
|
||||
let updatedNotification: Notification = {
|
||||
description: notify.description,
|
||||
url: notify.url,
|
||||
notifierUserId: notify.notifierUserId,
|
||||
notifyRecieverUserId: uid,
|
||||
isSeen: true
|
||||
}
|
||||
|
||||
return notificationService.setSeenNotification(id,uid,updatedNotification)
|
||||
.then(() => {
|
||||
dispatch(seenNotify(id))
|
||||
})
|
||||
.catch((error) => dispatch(globalActions.showErrorMessage(error.message)))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* _____________ CRUD State _____________ */
|
||||
|
||||
/**
|
||||
* Add notificaition
|
||||
*/
|
||||
export const addNotify = () => {
|
||||
|
||||
return {
|
||||
type: NotificationActionType.ADD_NOTIFY
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add notificaition list
|
||||
* @param {[notifyId: string]: Notification} userNotifies an array of notificaitions
|
||||
*/
|
||||
export const addNotifyList = (userNotifies: {[notifyId: string]: Notification}) => {
|
||||
|
||||
return {
|
||||
type: NotificationActionType.ADD_NOTIFY_LIST,
|
||||
payload: userNotifies
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a notificaition
|
||||
* @param {string} id of notificaition
|
||||
*/
|
||||
export const deleteNotify = (id: string) => {
|
||||
return { type: NotificationActionType.DELETE_NOTIFY, payload: id }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Change notificaition to has seen status
|
||||
* @param {string} id of notificaition
|
||||
*/
|
||||
export const seenNotify = (id: string) => {
|
||||
return { type: NotificationActionType.SEEN_NOTIFY, payload: id }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all data
|
||||
*/
|
||||
export const clearAllNotifications = () => {
|
||||
return {
|
||||
type: NotificationActionType.CLEAR_ALL_DATA_NOTIFY
|
||||
}
|
||||
}
|
||||
316
src/actions/postActions.ts
Normal file
316
src/actions/postActions.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
// - Import react components
|
||||
import { Action } from 'redux'
|
||||
|
||||
// - Import domain
|
||||
import { Post } from 'core/domain/posts'
|
||||
import { SocialError } from 'core/domain/common'
|
||||
|
||||
// - Import utility components
|
||||
import moment from 'moment'
|
||||
|
||||
// - Import action types
|
||||
import { PostActionType } from 'constants/postActionType'
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
|
||||
import { IServiceProvider, ServiceProvide } from 'core/factories'
|
||||
import { IPostService } from 'core/services/posts'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const postService: IPostService = serviceProvider.createPostService()
|
||||
|
||||
/* _____________ CRUD DB _____________ */
|
||||
|
||||
/**
|
||||
* Add a normal post
|
||||
* @param {any} newPost
|
||||
* @param {Function} callBack
|
||||
*/
|
||||
export let dbAddPost = (newPost: any, callBack: Function) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
let uid: string = getState().authorize.uid
|
||||
let post: Post = {
|
||||
postTypeId: 0,
|
||||
creationDate: moment().unix(),
|
||||
deleteDate: 0,
|
||||
score: 0,
|
||||
viewCount: 0,
|
||||
body: newPost.body,
|
||||
ownerUserId: uid,
|
||||
ownerDisplayName: newPost.name,
|
||||
ownerAvatar: newPost.avatar,
|
||||
lastEditDate: 0,
|
||||
tags: newPost.tags || [],
|
||||
commentCounter: 0,
|
||||
image: '',
|
||||
imageFullPath: '',
|
||||
video: '',
|
||||
disableComments: newPost.disableComments,
|
||||
disableSharing: newPost.disableSharing,
|
||||
deleted: false
|
||||
}
|
||||
|
||||
return postService.addPost(uid,post).then((postKey: string) => {
|
||||
dispatch(addPost(uid, {
|
||||
...post,
|
||||
id: postKey
|
||||
}))
|
||||
callBack()
|
||||
})
|
||||
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a post with image
|
||||
* @param {object} newPost
|
||||
* @param {function} callBack
|
||||
*/
|
||||
export const dbAddImagePost = (newPost: Post, callBack: Function) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
dispatch(globalActions.showTopLoading())
|
||||
|
||||
let uid: string = getState().authorize.uid
|
||||
let post: Post = {
|
||||
postTypeId: 1,
|
||||
creationDate: moment().unix(),
|
||||
deleteDate: 0,
|
||||
score: 0,
|
||||
viewCount: 0,
|
||||
body: newPost.body,
|
||||
ownerUserId: uid,
|
||||
ownerDisplayName: newPost.ownerDisplayName,
|
||||
ownerAvatar: newPost.ownerAvatar,
|
||||
lastEditDate: 0,
|
||||
tags: newPost.tags || [],
|
||||
commentCounter: 0,
|
||||
image: newPost.image || '',
|
||||
imageFullPath: newPost.imageFullPath || '',
|
||||
video: '',
|
||||
disableComments: newPost.disableComments ? newPost.disableComments : false,
|
||||
disableSharing: newPost.disableSharing ? newPost.disableSharing : false,
|
||||
deleted: false
|
||||
}
|
||||
|
||||
return postService.addPost(uid,post).then((postKey: string) => {
|
||||
dispatch(addPost(uid, {
|
||||
...post,
|
||||
id: postKey
|
||||
}))
|
||||
callBack()
|
||||
dispatch(globalActions.hideTopLoading())
|
||||
|
||||
})
|
||||
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a post from database
|
||||
* @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)
|
||||
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(uid,newPost.id,updatedPost).then(() => {
|
||||
|
||||
dispatch(updatePost(uid, { id: newPost.id, ...updatedPost }))
|
||||
callBack()
|
||||
dispatch(globalActions.hideTopLoading())
|
||||
|
||||
})
|
||||
.catch((error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
dispatch(globalActions.hideTopLoading())
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a post from database
|
||||
* @param {string} id is post identifier
|
||||
*/
|
||||
export const dbDeletePost = (id: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
dispatch(globalActions.showTopLoading())
|
||||
|
||||
// Get current user id
|
||||
let uid: string = getState().authorize.uid
|
||||
|
||||
return postService.deletePost(uid,id).then(() => {
|
||||
dispatch(deletePost(uid, id))
|
||||
dispatch(globalActions.hideTopLoading())
|
||||
|
||||
})
|
||||
.catch((error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
dispatch(globalActions.hideTopLoading())
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all user posts from data base
|
||||
*/
|
||||
export const dbGetPosts = () => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
let uid: string = getState().authorize.uid
|
||||
if (uid) {
|
||||
|
||||
return postService.getPosts(uid).then((posts: { [postId: string]: Post }) => {
|
||||
dispatch(addPosts(uid, posts))
|
||||
})
|
||||
.catch((error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all user posts from data base
|
||||
* @param uid post owner identifier
|
||||
* @param postId post identifier
|
||||
*/
|
||||
export const dbGetPostById = (uid: string, postId: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
if (uid) {
|
||||
|
||||
return postService.getPostById(uid,postId).then((post: Post) => {
|
||||
dispatch(addPost(uid, post))
|
||||
})
|
||||
.catch((error: SocialError) => {
|
||||
dispatch(globalActions.showErrorMessage(error.message))
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all user posts from data base by user id
|
||||
* @param uid posts owner identifier
|
||||
*/
|
||||
export const dbGetPostsByUserId = (uid: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
if (uid) {
|
||||
return postService.getPosts(uid).then((posts: { [postId: string]: Post }) => {
|
||||
dispatch(addPosts(uid, posts))
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* _____________ CRUD State _____________ */
|
||||
|
||||
/**
|
||||
* Add a normal post
|
||||
* @param {string} uid is user identifier
|
||||
* @param {Post} post
|
||||
*/
|
||||
export const addPost = (uid: string, post: Post) => {
|
||||
return {
|
||||
type: PostActionType.ADD_POST,
|
||||
payload: { uid, post }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a post
|
||||
* @param {string} uid is user identifier
|
||||
* @param {Post} post
|
||||
*/
|
||||
export const updatePost = (uid: string, post: Post) => {
|
||||
return {
|
||||
type: PostActionType.UPDATE_POST,
|
||||
payload: { uid, post }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a post
|
||||
* @param {string} uid is user identifier
|
||||
* @param {string} id is post identifier
|
||||
*/
|
||||
export const deletePost = (uid: string, id: string) => {
|
||||
return {
|
||||
type: PostActionType.DELETE_POST,
|
||||
payload: { uid, id }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a list of post
|
||||
* @param {string} uid
|
||||
* @param {[object]} posts
|
||||
*/
|
||||
export const addPosts = (uid: string, posts: { [postId: string]: Post }) => {
|
||||
return {
|
||||
type: PostActionType.ADD_LIST_POST,
|
||||
payload: { uid, posts }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clea all data in post store
|
||||
*/
|
||||
export const clearAllData = () => {
|
||||
return {
|
||||
type: PostActionType.CLEAR_ALL_DATA_POST
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a post with image
|
||||
* @param {object} post
|
||||
*/
|
||||
export const addImagePost = (uid: string, post: any) => {
|
||||
return {
|
||||
type: PostActionType.ADD_IMAGE_POST,
|
||||
payload: { uid, post }
|
||||
}
|
||||
|
||||
}
|
||||
201
src/actions/userActions.ts
Normal file
201
src/actions/userActions.ts
Normal file
@@ -0,0 +1,201 @@
|
||||
// - Import react components
|
||||
|
||||
// - Import domain
|
||||
import { Profile } from 'core/domain/users'
|
||||
import { SocialError } from 'core/domain/common'
|
||||
|
||||
// - Import action types
|
||||
import { UserActionType } from 'constants/userActionType'
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as userActions from 'actions/userActions'
|
||||
|
||||
import { IServiceProvider, ServiceProvide } from 'core/factories'
|
||||
import { IUserService } from 'core/services/users'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const userService: IUserService = serviceProvider.createUserService()
|
||||
|
||||
/* _____________ CRUD DB _____________ */
|
||||
|
||||
/**
|
||||
* Get user info from database
|
||||
*/
|
||||
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
|
||||
}))
|
||||
})
|
||||
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user info from database
|
||||
* @param {string} uid
|
||||
* @param {string} callerKey
|
||||
*/
|
||||
export const dbGetUserInfoByUserId = (uid: string, callerKey: string) => {
|
||||
return (dispatch: Function, getState: Function) => {
|
||||
if (uid) {
|
||||
|
||||
let caller = getState().global.temp.caller
|
||||
if ( caller && caller.indexOf(`dbGetUserInfoByUserId-${uid}`) > -1) {
|
||||
return
|
||||
}
|
||||
dispatch(globalActions.temp({caller: `dbGetUserInfoByUserId-${uid}`}))
|
||||
return userService.getUserProfile(uid).then((userProfile: Profile) => {
|
||||
|
||||
dispatch(addUserInfo(uid, {
|
||||
avatar: userProfile.avatar,
|
||||
email: userProfile.email,
|
||||
fullName: userProfile.fullName,
|
||||
banner: userProfile.banner,
|
||||
tagLine: userProfile.tagLine
|
||||
}))
|
||||
|
||||
switch (callerKey) {
|
||||
case 'header':
|
||||
dispatch(globalActions.setHeaderTitle(userProfile.fullName))
|
||||
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Updata user information
|
||||
* @param {object} newInfo
|
||||
*/
|
||||
export const dbUpdateUserInfo = (newProfile: Profile) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
// Get current user id
|
||||
let uid: string = getState().authorize.uid
|
||||
|
||||
let profile: Profile = getState().user.info[uid]
|
||||
let updatedProfie: Profile = {
|
||||
avatar: newProfile.avatar || profile.avatar || '',
|
||||
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 || ''
|
||||
}
|
||||
|
||||
return userService.updateUserProfile(uid,updatedProfie).then(() => {
|
||||
|
||||
dispatch(updateUserInfo(uid, updatedProfie))
|
||||
dispatch(closeEditProfile())
|
||||
})
|
||||
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// - Get people info from database
|
||||
export const dbGetPeopleInfo = () => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
let uid: string = getState().authorize.uid
|
||||
if (uid) {
|
||||
return userService.getUsersProfile(uid)
|
||||
.then((usersProfile: {[userId: string]: Profile}) => {
|
||||
dispatch(addPeopleInfo(usersProfile))
|
||||
})
|
||||
.catch((error: SocialError) => dispatch(globalActions.showErrorMessage(error.message)))
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* _____________ CRUD State _____________ */
|
||||
|
||||
/**
|
||||
* Add user information
|
||||
* @param {string} uid is the user identifier
|
||||
* @param {Profile} info is the information about user
|
||||
*/
|
||||
export const addUserInfo = (uid: string, info: Profile) => {
|
||||
return {
|
||||
type: UserActionType.ADD_USER_INFO,
|
||||
payload: { uid, info }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add people information
|
||||
* @param {[userId: string]: Profile} infoList is the lst of information about users
|
||||
*/
|
||||
export const addPeopleInfo = (infoList: {[userId: string]: Profile}) => {
|
||||
return {
|
||||
type: UserActionType.ADD_PEOPLE_INFO,
|
||||
payload: infoList
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user information
|
||||
* @param {string} uid is the user identifier
|
||||
* @param {Profile} info is the information about user
|
||||
*/
|
||||
export const updateUserInfo = (uid: string, info: Profile) => {
|
||||
return {
|
||||
type: UserActionType.UPDATE_USER_INFO,
|
||||
payload: { uid, info }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User info
|
||||
* @param {Profile} info
|
||||
*/
|
||||
export const userInfo = (info: Profile) => {
|
||||
return {
|
||||
type: UserActionType.USER_INFO,
|
||||
info
|
||||
}
|
||||
}
|
||||
|
||||
export const clearAllData = () => {
|
||||
return {
|
||||
type: UserActionType.CLEAR_ALL_DATA_USER
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open edit profile
|
||||
*/
|
||||
export const openEditProfile = () => {
|
||||
return {
|
||||
type: UserActionType.OPEN_EDIT_PROFILE
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Close edit profile
|
||||
*/
|
||||
export const closeEditProfile = () => {
|
||||
return {
|
||||
type: UserActionType.CLOSE_EDIT_PROFILE
|
||||
}
|
||||
|
||||
}
|
||||
132
src/actions/voteActions.ts
Normal file
132
src/actions/voteActions.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import moment from 'moment'
|
||||
|
||||
// - Import action types
|
||||
import { VoteActionType } from 'constants/voteActionType'
|
||||
|
||||
// - Import domain
|
||||
import { Vote } from 'core/domain/votes'
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as notifyActions from 'actions/notifyActions'
|
||||
|
||||
import { IServiceProvider, ServiceProvide } from 'core/factories'
|
||||
import { IVoteService } from 'core/services/votes'
|
||||
|
||||
const serviceProvider: IServiceProvider = new ServiceProvide()
|
||||
const voteService: IVoteService = serviceProvider.createVoteService()
|
||||
|
||||
/* _____________ CRUD DB _____________ */
|
||||
|
||||
/**
|
||||
* Add vote to database
|
||||
* @param {string} postId is the identifier of the post which user vote
|
||||
* @param {string} ownerPostUserId is the identifier of the post owner which user vote
|
||||
*/
|
||||
export const dbAddVote = (postId: string,ownerPostUserId: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
let uid: string = getState().authorize.uid
|
||||
let vote: Vote = {
|
||||
postId: postId,
|
||||
creationDate: moment().unix(),
|
||||
userDisplayName: getState().user.info[uid].fullName,
|
||||
userAvatar: getState().user.info[uid].avatar,
|
||||
userId: uid
|
||||
}
|
||||
|
||||
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,
|
||||
isSeen: false
|
||||
}))
|
||||
}
|
||||
|
||||
})
|
||||
.catch((error) => dispatch(globalActions.showErrorMessage(error.message)))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all votes from database
|
||||
*/
|
||||
export const dbGetVotes = () => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
let uid: string = getState().authorize.uid
|
||||
if (uid) {
|
||||
|
||||
return voteService
|
||||
.getVotes()
|
||||
.then((postVotes: { [postId: string]: { [voteId: string]: Vote } }) => {
|
||||
dispatch(addVoteList(postVotes))
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a vote from database
|
||||
* @param {string} id of vote
|
||||
* @param {string} postId is the identifier of the post which vote belong to
|
||||
*/
|
||||
export const dbDeleteVote = (postId: string) => {
|
||||
return (dispatch: any, getState: Function) => {
|
||||
|
||||
// Get current user id
|
||||
let uid: string = getState().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))
|
||||
})
|
||||
.catch((error: any) => dispatch(globalActions.showErrorMessage(error.message)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a vote
|
||||
* @param {Vote} vote
|
||||
*/
|
||||
export const addVote = (vote: Vote) => {
|
||||
return { type: VoteActionType.ADD_VOTE, payload: vote }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* delete a vote
|
||||
* @param {string} id vote identifier
|
||||
* @param {string} postId post identifier which vote on
|
||||
*/
|
||||
export const deleteVote = (id: string, postId: string) => {
|
||||
return { type: VoteActionType.DELETE_VOTE, payload: {id, postId} }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ad 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 }
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all data
|
||||
*/
|
||||
export const clearAllvotes = () => {
|
||||
return { type: VoteActionType.CLEAR_ALL_DATA_VOTE }
|
||||
}
|
||||
47
src/api/CircleAPI.ts
Normal file
47
src/api/CircleAPI.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { Circle, UserFollower } from 'core/domain/circles'
|
||||
|
||||
/**
|
||||
* Get the circles' id which the specify users is in that circle
|
||||
* @param {object} circles
|
||||
* @param {string} followingId
|
||||
*/
|
||||
export const getUserBelongCircles = (circles: {[circleId: string]: Circle},followingId: string) => {
|
||||
let userBelongCircles: string[] = []
|
||||
Object.keys(circles).forEach((cid) => {
|
||||
if (cid.trim() !== '-Followers' && circles[cid].users) {
|
||||
let isExist = Object.keys(circles[cid].users).indexOf(followingId) > -1
|
||||
if (isExist) {
|
||||
userBelongCircles.push(cid)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return userBelongCircles
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the following users
|
||||
* @param {object} circles
|
||||
*/
|
||||
export const getFollowingUsers = (circles: {[circleId: string]: Circle}) => {
|
||||
let followingUsers: {[userId: string]: UserFollower} = {}
|
||||
Object.keys(circles).forEach((cid) => {
|
||||
if (cid.trim() !== '-Followers' && circles[cid].users) {
|
||||
Object.keys(circles[cid].users).forEach((userId) => {
|
||||
let isExist = Object.keys(followingUsers).indexOf(userId) > -1
|
||||
if (!isExist) {
|
||||
followingUsers[userId] = {
|
||||
...circles[cid].users[userId]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
})
|
||||
return followingUsers
|
||||
}
|
||||
|
||||
export default {
|
||||
getUserBelongCircles,
|
||||
getFollowingUsers
|
||||
}
|
||||
147
src/api/FileAPI.ts
Normal file
147
src/api/FileAPI.ts
Normal file
@@ -0,0 +1,147 @@
|
||||
// - Import react component
|
||||
import { storageRef } from 'data/firebaseClient'
|
||||
|
||||
// - Interface declaration
|
||||
interface FileReaderEventTarget extends EventTarget {
|
||||
result: string
|
||||
}
|
||||
|
||||
interface FileReaderEvent extends Event {
|
||||
target: FileReaderEventTarget
|
||||
getMessage (): string
|
||||
}
|
||||
|
||||
// - Get file Extension
|
||||
const getExtension = (fileName: string) => {
|
||||
let re: RegExp = /(?:\.([^.]+))?$/
|
||||
return re.exec(fileName)![1]
|
||||
}
|
||||
|
||||
// Converts image to canvas returns new canvas element
|
||||
const convertImageToCanvas = (image: HTMLImageElement | HTMLCanvasElement | HTMLVideoElement | ImageBitmap) => {
|
||||
let canvas = document.createElement('canvas')
|
||||
canvas.width = image.width
|
||||
canvas.height = image.height
|
||||
canvas.getContext('2d')!.drawImage(image, 0, 0)
|
||||
|
||||
return canvas
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload image on the server
|
||||
* @param {file} file
|
||||
* @param {string} fileName
|
||||
*/
|
||||
const uploadImage = (file: any, fileName: string, progress: (percentage: number, status: boolean) => void) => {
|
||||
|
||||
return new Promise<any>((resolve, reject) => {
|
||||
// Create a storage refrence
|
||||
let storegeFile = storageRef.child(`images/${fileName}`)
|
||||
|
||||
// Upload file
|
||||
let task = storegeFile.put(file)
|
||||
task.then((result) => {
|
||||
resolve(result)
|
||||
}).catch((error) => {
|
||||
reject(error)
|
||||
})
|
||||
|
||||
// Upload storage bar
|
||||
task.on('state_changed', (snapshot: any) => {
|
||||
let percentage: number = (snapshot.bytesTransferred / snapshot.totalBytes) * 100
|
||||
progress(percentage, true)
|
||||
}, (error) => {
|
||||
console.log('========== Upload Image ============')
|
||||
console.log(error)
|
||||
console.log('====================================')
|
||||
|
||||
}, () => {
|
||||
progress(100, false)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Constraint image size
|
||||
* @param {file} file
|
||||
* @param {number} maxWidth
|
||||
* @param {number} maxHeight
|
||||
*/
|
||||
const constraintImage = (file: File,fileName: string, maxWidth?: number, maxHeight?: number) => {
|
||||
// Ensure it's an image
|
||||
if (file.type.match(/image.*/)) {
|
||||
// Load the image
|
||||
let reader = new FileReader()
|
||||
reader.onload = function (readerEvent: FileReaderEvent) {
|
||||
let image = new Image()
|
||||
image.onload = function (imageEvent: Event) {
|
||||
|
||||
// Resize the image
|
||||
let canvas: HTMLCanvasElement = document.createElement('canvas')
|
||||
let maxSize: number = 986// TODO : pull max size from a site config
|
||||
let width: number = image.width
|
||||
let height: number = image.height
|
||||
if (width > height) {
|
||||
if (width > maxSize) {
|
||||
height *= maxSize / width
|
||||
width = maxSize
|
||||
}
|
||||
} else {
|
||||
if (height > maxSize) {
|
||||
width *= maxSize / height
|
||||
height = maxSize
|
||||
}
|
||||
}
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
canvas.getContext('2d')!.drawImage(image, 0, 0, width, height)
|
||||
let dataUrl = canvas.toDataURL()
|
||||
let resizedImage = dataURLToBlob(dataUrl)
|
||||
let evt = new CustomEvent('onSendResizedImage', { detail: {resizedImage,fileName} })
|
||||
window.dispatchEvent(evt)
|
||||
|
||||
}
|
||||
image.src = readerEvent.target.result
|
||||
}
|
||||
reader.readAsDataURL(file)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert data URL to blob
|
||||
* @param {object} dataURL
|
||||
*/
|
||||
const dataURLToBlob = (dataURL: string) => {
|
||||
|
||||
let BASE64_MARKER = 'base64,'
|
||||
if (dataURL.indexOf(BASE64_MARKER) === -1) {
|
||||
let parts = dataURL.split(',')
|
||||
let contentType = parts[0].split(':')[1]
|
||||
let raw = parts[1]
|
||||
|
||||
return new Blob([raw], {type: contentType})
|
||||
}
|
||||
|
||||
let parts = dataURL.split(BASE64_MARKER)
|
||||
let contentType = parts[0].split(':')[1]
|
||||
let raw = window.atob(parts[1])
|
||||
let rawLength = raw.length
|
||||
|
||||
let uInt8Array = new Uint8Array(rawLength)
|
||||
|
||||
for (let i = 0 ;i < rawLength; ++i) {
|
||||
uInt8Array[i] = raw.charCodeAt(i)
|
||||
}
|
||||
|
||||
return new Blob([uInt8Array], {type: contentType})
|
||||
}
|
||||
|
||||
export default {
|
||||
dataURLToBlob,
|
||||
convertImageToCanvas,
|
||||
getExtension,
|
||||
constraintImage,
|
||||
uploadImage
|
||||
|
||||
}
|
||||
29
src/api/PostAPI.ts
Normal file
29
src/api/PostAPI.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
|
||||
// Get tags from post content
|
||||
export const detectTags: (content: string, character: string) => string[] = (content: string, character: string) => {
|
||||
|
||||
return content.split(' ').filter((word) => {
|
||||
return (word.slice(0,1) === character)
|
||||
})
|
||||
|
||||
}
|
||||
export const getContentTags = (content: string) => {
|
||||
let newTags: string[] = []
|
||||
let tags = detectTags(content,'#')
|
||||
tags.forEach((tag) => {
|
||||
newTags.push(tag.slice(1))
|
||||
})
|
||||
return newTags
|
||||
}
|
||||
|
||||
export const sortObjectsDate = (objects: any) => {
|
||||
let sortedObjects = objects
|
||||
|
||||
// Sort posts with creation date
|
||||
sortedObjects.sort((a: any, b: any) => {
|
||||
return parseInt(b.creationDate,10) - parseInt(a.creationDate,10)
|
||||
|
||||
})
|
||||
|
||||
return sortedObjects
|
||||
}
|
||||
30
src/api/StringAPI.ts
Normal file
30
src/api/StringAPI.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
// - Import react component
|
||||
import { storageRef } from 'data/firebaseClient'
|
||||
|
||||
// - Import actions
|
||||
|
||||
const isValidEmail = (email: string) => {
|
||||
let re = /^(([^<>()\[\]\\.,:\s@"]+(\.[^<>()\[\]\\.,:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||
return re.test(email)
|
||||
}
|
||||
|
||||
function queryString (name: string, url: string = window.location.href) {
|
||||
name = name.replace(/[[]]/g, '\\$&')
|
||||
|
||||
const regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)', 'i')
|
||||
const results = regex.exec(url)
|
||||
|
||||
if (!results) {
|
||||
return null
|
||||
}
|
||||
if (!results[2]) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return decodeURIComponent(results[2].replace(/\+/g, ' '))
|
||||
}
|
||||
|
||||
export default {
|
||||
isValidEmail,
|
||||
queryString
|
||||
}
|
||||
270
src/components/circle/CircleComponent.tsx
Normal file
270
src/components/circle/CircleComponent.tsx
Normal file
@@ -0,0 +1,270 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { push } from 'react-router-redux'
|
||||
import { grey400, darkBlack, lightBlack } from 'material-ui/styles/colors'
|
||||
import { List, ListItem } from 'material-ui/List'
|
||||
import SvgGroup from 'material-ui/svg-icons/action/group-work'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'
|
||||
import IconMenu from 'material-ui/IconMenu'
|
||||
import TextField from 'material-ui/TextField'
|
||||
import MenuItem from 'material-ui/MenuItem'
|
||||
import IconButtonElement from 'layouts/IconButtonElement'
|
||||
import Dialog from 'material-ui/Dialog'
|
||||
import Divider from 'material-ui/Divider'
|
||||
import FlatButton from 'material-ui/FlatButton'
|
||||
import RaisedButton from 'material-ui/RaisedButton'
|
||||
import SvgClose from 'material-ui/svg-icons/navigation/close'
|
||||
import AppBar from 'material-ui/AppBar'
|
||||
|
||||
// - Import app components
|
||||
import UserAvatar from 'components/userAvatar'
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as circleActions from 'actions/circleActions'
|
||||
|
||||
import { ICircleComponentProps } from './ICircleComponentProps'
|
||||
import { ICircleComponentState } from './ICircleComponentState'
|
||||
import { Circle } from 'core/domain/circles'
|
||||
import { Profile } from 'core/domain/users/profile'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class CircleComponent extends Component<ICircleComponentProps, ICircleComponentState> {
|
||||
|
||||
styles = {
|
||||
userListItem: {
|
||||
backgroundColor: '#e2e2e2'
|
||||
},
|
||||
rightIconMenu: {
|
||||
display: 'block',
|
||||
position: 'absolute',
|
||||
top: '0px',
|
||||
right: '12px'
|
||||
},
|
||||
settingOverlay: {
|
||||
background: 'rgba(0,0,0,0.12)'
|
||||
},
|
||||
settingContent: {
|
||||
maxWidth: '400px'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: ICircleComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Defaul state
|
||||
this.state = {
|
||||
/**
|
||||
* If is true circle is open to show users in circle list
|
||||
*/
|
||||
open: false,
|
||||
/**
|
||||
* Circle name on change
|
||||
*/
|
||||
circleName: this.props.circle.name,
|
||||
/**
|
||||
* Save operation will be disable if user doesn't meet requirement
|
||||
*/
|
||||
disabledSave: false
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
this.handleToggleCircle = this.handleToggleCircle.bind(this)
|
||||
this.handleDeleteCircle = this.handleDeleteCircle.bind(this)
|
||||
this.handleUpdateCircle = this.handleUpdateCircle.bind(this)
|
||||
this.handleChangeCircleName = this.handleChangeCircleName.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle chage circle name
|
||||
*
|
||||
*
|
||||
* @memberof CircleComponent
|
||||
*/
|
||||
handleChangeCircleName = (evt: any) => {
|
||||
const { value } = evt.target
|
||||
this.setState({
|
||||
circleName: value,
|
||||
disabledSave: (!value || value.trim() === '')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user's circle
|
||||
*
|
||||
*
|
||||
* @memberof CircleComponent
|
||||
*/
|
||||
handleUpdateCircle = () => {
|
||||
const { circleName } = this.state
|
||||
if (circleName && circleName.trim() !== '') {
|
||||
this.props.updateCircle!({ name: circleName, id: this.props.id })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle delete circle
|
||||
*
|
||||
*
|
||||
* @memberof CircleComponent
|
||||
*/
|
||||
handleDeleteCircle = () => {
|
||||
this.props.deleteCircle!(this.props.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle circle to close/open
|
||||
*
|
||||
*
|
||||
* @memberof CircleComponent
|
||||
*/
|
||||
handleToggleCircle = () => {
|
||||
this.setState({
|
||||
open: !this.state.open
|
||||
})
|
||||
}
|
||||
|
||||
userList = () => {
|
||||
const { users } = this.props.circle
|
||||
const { userInfo } = this.props
|
||||
let usersParsed: any = []
|
||||
|
||||
if (users) {
|
||||
Object.keys(users).forEach((key, index) => {
|
||||
const { fullName } = users[key]
|
||||
let avatar = userInfo && userInfo[key] ? userInfo[key].avatar || '' : ''
|
||||
usersParsed.push(<ListItem
|
||||
key={`${this.props.id}.${key}`}
|
||||
style={this.styles.userListItem as any}
|
||||
value={2}
|
||||
primaryText={fullName}
|
||||
leftAvatar={<UserAvatar fullName={fullName} fileName={avatar as any} />}
|
||||
onClick={() => this.props.goTo!(`/${key}`)}
|
||||
/>)
|
||||
|
||||
})
|
||||
return usersParsed
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Right icon menue of circle
|
||||
*
|
||||
*
|
||||
* @memberof CircleComponent
|
||||
*/
|
||||
// tslint:disable-next-line:member-ordering
|
||||
rightIconMenu: any = (
|
||||
<IconMenu iconButtonElement={IconButtonElement} style={this.styles.rightIconMenu as any}>
|
||||
<MenuItem primaryText='Delete circle' onClick={this.handleDeleteCircle} />
|
||||
<MenuItem primaryText='Circle settings' onClick={this.props.openCircleSettings} />
|
||||
</IconMenu>
|
||||
)
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
|
||||
const circleTitle = (
|
||||
<div>
|
||||
<div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-between' }}>
|
||||
<div style={{ paddingRight: '10px' }}>
|
||||
<SvgClose onClick={this.props.closeCircleSettings} hoverColor={grey400} style={{ cursor: 'pointer' }} />
|
||||
</div>
|
||||
<div style={{
|
||||
color: 'rgba(0,0,0,0.87)',
|
||||
flex: '1 1',
|
||||
font: '500 20px Roboto,RobotoDraft,Helvetica,Arial,sans-serif'
|
||||
}}>
|
||||
Circle settings
|
||||
</div>
|
||||
<div style={{ marginTop: '-9px' }}>
|
||||
<FlatButton label='SAVE' primary={true} disabled={this.state.disabledSave} onClick={this.handleUpdateCircle} />
|
||||
</div>
|
||||
</div>
|
||||
<Divider />
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div>
|
||||
<ListItem
|
||||
key={this.props.id}
|
||||
style={{ backgroundColor: '#fff', borderBottom: '1px solid rgba(0,0,0,0.12)', height: '72px', padding: '12px 0' }}
|
||||
primaryText={<span style={{ color: 'rgba(0,0,0,0.87)', fontSize: '16px', marginRight: '8px', whiteSpace: 'nowrap', textOverflow: 'ellipsis', overflow: 'hidden' }}>{this.props.circle.name}</span>}
|
||||
leftIcon={<SvgGroup style={{ width: '40px', height: '40px', transform: 'translate(0px, -9px)', fill: '#bdbdbd' }} />}
|
||||
rightIconButton={this.rightIconMenu}
|
||||
initiallyOpen={false}
|
||||
onClick={this.handleToggleCircle}
|
||||
open={this.state.open}
|
||||
nestedItems={this.userList()}
|
||||
>
|
||||
<Dialog
|
||||
key={this.props.id}
|
||||
title={circleTitle}
|
||||
modal={false}
|
||||
open={this.props.openSetting!}
|
||||
onRequestClose={this.props.closeCircleSettings}
|
||||
overlayStyle={this.styles.settingOverlay as any}
|
||||
contentStyle={this.styles.settingContent as any}
|
||||
>
|
||||
<div>
|
||||
<TextField
|
||||
hintText='Circle name'
|
||||
floatingLabelText='Circle name'
|
||||
onChange={this.handleChangeCircleName}
|
||||
value={this.state.circleName}
|
||||
/>
|
||||
</div>
|
||||
</Dialog>
|
||||
</ListItem>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: ICircleComponentProps) => {
|
||||
let { uid } = ownProps
|
||||
return {
|
||||
deleteCircle: (id: string) => dispatch(circleActions.dbDeleteCircle(id)),
|
||||
updateCircle: (circle: Circle) => dispatch(circleActions.dbUpdateCircle(circle)),
|
||||
closeCircleSettings: () => dispatch(circleActions.closeCircleSettings(uid, ownProps.id)),
|
||||
openCircleSettings: () => dispatch(circleActions.openCircleSettings(uid, ownProps.id)),
|
||||
goTo: (url: string) => dispatch(push(url))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: ICircleComponentProps) => {
|
||||
let { uid } = state.authorize
|
||||
return {
|
||||
openSetting: state.circle ? (state.circle.userCircles[uid] ? (state.circle.userCircles[uid][ownProps.id].openCircleSettings || false) : false) : false,
|
||||
userInfo: state.user.info
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CircleComponent as any)
|
||||
84
src/components/circle/ICircleComponentProps.ts
Normal file
84
src/components/circle/ICircleComponentProps.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import { Comment } from 'core/domain/comments'
|
||||
import { Profile } from 'core/domain/users'
|
||||
import { Circle } from 'core/domain/circles'
|
||||
|
||||
export interface ICircleComponentProps {
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @type {Circle}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
circle: Circle
|
||||
|
||||
/**
|
||||
* Circle identifier
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
id: string
|
||||
|
||||
/**
|
||||
* User identifier
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
uid: string
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
updateCircle?: Function
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
deleteCircle?: Function
|
||||
|
||||
/**
|
||||
* Users profile
|
||||
*
|
||||
* @type {{[userId: string]: Profile}}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
userInfo?: {[userId: string]: Profile}
|
||||
|
||||
/**
|
||||
* Close setting box of circle
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
closeCircleSettings?: any
|
||||
|
||||
/**
|
||||
* Circle setting dialog is open {true} or not {false}
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
openSetting?: boolean
|
||||
|
||||
/**
|
||||
* Change route location
|
||||
*
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
goTo?: (url: string) => void
|
||||
|
||||
/**
|
||||
* Open setting box for a circle
|
||||
*
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
openCircleSettings?: () => any
|
||||
}
|
||||
27
src/components/circle/ICircleComponentState.ts
Normal file
27
src/components/circle/ICircleComponentState.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
export interface ICircleComponentState {
|
||||
|
||||
/**
|
||||
* Circle name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICircleComponentState
|
||||
*/
|
||||
circleName: string
|
||||
|
||||
/**
|
||||
* If circle user list is open {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICircleComponentState
|
||||
*/
|
||||
open: boolean
|
||||
|
||||
/**
|
||||
* Save button is disabled {true} or not false
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICircleComponentState
|
||||
*/
|
||||
disabledSave: boolean
|
||||
}
|
||||
2
src/components/circle/index.ts
Normal file
2
src/components/circle/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import CircleComponent from './CircleComponent'
|
||||
export default CircleComponent
|
||||
335
src/components/comment/CommentComponent.tsx
Normal file
335
src/components/comment/CommentComponent.tsx
Normal file
@@ -0,0 +1,335 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import moment from 'moment'
|
||||
import Linkify from 'react-linkify'
|
||||
|
||||
// - Import material UI libraries
|
||||
import { List, ListItem } from 'material-ui/List'
|
||||
import Divider from 'material-ui/Divider'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import FlatButton from 'material-ui/FlatButton'
|
||||
import { grey400, darkBlack, lightBlack } from 'material-ui/styles/colors'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'
|
||||
import IconMenu from 'material-ui/IconMenu'
|
||||
import MenuItem from 'material-ui/MenuItem'
|
||||
import TextField from 'material-ui/TextField'
|
||||
|
||||
// - Import app components
|
||||
import UserAvatarComponent from 'components/userAvatar'
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import action types
|
||||
import * as types from 'constants/actionTypes'
|
||||
|
||||
// - Import actions
|
||||
import * as commentActions from 'actions/commentActions'
|
||||
import * as userActions from 'actions/userActions'
|
||||
|
||||
import { ICommentComponentProps } from './ICommentComponentProps'
|
||||
import { ICommentComponentState } from './ICommentComponentState'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class CommentComponent extends Component<ICommentComponentProps,ICommentComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
/**
|
||||
* Comment object
|
||||
*/
|
||||
comment: PropTypes.object,
|
||||
/**
|
||||
* If it's true the post owner is the logged in user which this post be long to the comment
|
||||
*/
|
||||
isPostOwner: PropTypes.bool,
|
||||
/**
|
||||
* If it's true the comment is disable to write
|
||||
*/
|
||||
disableComments: PropTypes.bool
|
||||
}
|
||||
|
||||
/**
|
||||
* DOM styles
|
||||
*
|
||||
*
|
||||
* @memberof CommentComponent
|
||||
*/
|
||||
styles = {
|
||||
comment: {
|
||||
marginBottom: '12px'
|
||||
},
|
||||
iconButton: {
|
||||
width: 16,
|
||||
height: 16
|
||||
|
||||
},
|
||||
author: {
|
||||
fontSize: '13px',
|
||||
paddingRight: '10px',
|
||||
fontWeight: 400,
|
||||
color: 'rgba(0,0,0,0.87)',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden'
|
||||
|
||||
},
|
||||
commentBody: {
|
||||
fontSize: '13px',
|
||||
lineHeight: '20px',
|
||||
color: 'rgba(0,0,0,0.87)',
|
||||
fontWeight: 300,
|
||||
height: '',
|
||||
display: 'block'
|
||||
|
||||
},
|
||||
rightIconMenuItem: {
|
||||
fontSize: '14px'
|
||||
},
|
||||
textarea: {
|
||||
fontWeight: 100,
|
||||
fontSize: '14px',
|
||||
border: 'none',
|
||||
width: '100%',
|
||||
outline: 'none',
|
||||
resize: 'none'
|
||||
},
|
||||
cancel: {
|
||||
float: 'right',
|
||||
clear: 'both',
|
||||
zIndex: 5,
|
||||
margin: '0px 5px 5px 0px',
|
||||
fontWeight: 400
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof CommentComponent
|
||||
*/
|
||||
textareaRef: any
|
||||
divCommentRef: any
|
||||
inputText: any
|
||||
divComment: any
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: ICommentComponentProps) {
|
||||
super(props)
|
||||
|
||||
this.textareaRef = (i: any) => { this.inputText = i }
|
||||
this.divCommentRef = (i: any) => { this.divComment = i }
|
||||
|
||||
// Defaul state
|
||||
this.state = {
|
||||
/**
|
||||
* Comment text
|
||||
*/
|
||||
text: this.props.comment.text,
|
||||
/**
|
||||
* Comment text to match edit with new comment that is edited
|
||||
*/
|
||||
initialText: this.props.comment.text,
|
||||
/**
|
||||
* If comment text dosn't take any change it will be true
|
||||
*/
|
||||
editDisabled: true,
|
||||
/**
|
||||
* If it's true the post owner is the logged in user which this post be long to the comment
|
||||
*/
|
||||
isPostOwner: false
|
||||
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
this.handleDelete = this.handleDelete.bind(this)
|
||||
this.handleUpdateComment = this.handleUpdateComment.bind(this)
|
||||
this.handleOnChange = this.handleOnChange.bind(this)
|
||||
this.handleCancelEdit = this.handleCancelEdit.bind(this)
|
||||
this.handleEditComment = this.handleEditComment.bind(this)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle show edit comment
|
||||
* @param {event} evt is an event passed by clicking on edit button
|
||||
*/
|
||||
handleEditComment = (evt: any) => {
|
||||
this.inputText.style.height = this.divComment.clientHeight + 'px'
|
||||
this.props.openEditor()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle cancel edit
|
||||
* @param {event} evt is an event passed by clicking on cancel button
|
||||
*/
|
||||
handleCancelEdit = (evt: any) => {
|
||||
|
||||
this.setState({
|
||||
text: this.state.initialText
|
||||
})
|
||||
this.props.closeEditor()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle edit comment
|
||||
* @param {event} evt is an event passed by clicking on post button
|
||||
*/
|
||||
handleUpdateComment = (evt: any) => {
|
||||
|
||||
this.props.update(this.props.comment.id, this.props.comment.postId, this.state.text)
|
||||
this.setState({
|
||||
initialText: this.state.text
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* When comment text changed
|
||||
* @param {event} evt is an event passed by change comment text callback funciton
|
||||
* @param {string} data is the comment text which user writes
|
||||
*/
|
||||
handleOnChange = (evt: any) => {
|
||||
const data = evt.target.value
|
||||
this.inputText.style.height = evt.target.scrollHeight + 'px'
|
||||
if (data.length === 0 || data.trim() === '' || data.trim() === this.state.initialText) {
|
||||
this.setState({
|
||||
text: data,
|
||||
editDisabled: true
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
text: data,
|
||||
editDisabled: false
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a comment
|
||||
* @param {event} evt an event passed by click on delete comment
|
||||
* @param {string} id comment identifire
|
||||
* @param {string} postId post identifier which comment belong to
|
||||
*/
|
||||
handleDelete = (evt: any, id?: string| null, postId?: string) => {
|
||||
this.props.delete(id, postId)
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
const {userId} = this.props.comment
|
||||
if (!this.props.isCommentOwner && !this.props.info[userId!]) {
|
||||
this.props.getUserInfo()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
|
||||
/**
|
||||
* Comment object from props
|
||||
*/
|
||||
const {comment} = this.props
|
||||
|
||||
const iconButtonElement = (
|
||||
<IconButton style={this.styles.iconButton} iconStyle={this.styles.iconButton}
|
||||
touch={true}
|
||||
>
|
||||
<MoreVertIcon color={grey400} viewBox='9 0 24 24' />
|
||||
</IconButton>
|
||||
)
|
||||
|
||||
const RightIconMenu = () => (
|
||||
<IconMenu iconButtonElement={iconButtonElement} style={{ display: 'block', position: 'absolute', top: '0px', right: '4px' }}>
|
||||
<MenuItem style={{ fontSize: '14px' }}>Reply</MenuItem>
|
||||
{this.props.isCommentOwner ? (<MenuItem style={this.styles.rightIconMenuItem} onClick={this.handleEditComment}>Edit</MenuItem>) : ''}
|
||||
{(this.props.isCommentOwner || this.props.isPostOwner) ? ( <MenuItem style={{ fontSize: '14px' }} onClick={(evt) => this.handleDelete(evt, comment.id, comment.postId)}>Delete</MenuItem>) : ''}
|
||||
</IconMenu>
|
||||
)
|
||||
|
||||
const Author = () => (
|
||||
<div style={{ marginTop: '-11px' }}>
|
||||
<span style={this.styles.author as any}>{comment.userDisplayName}</span><span style={{
|
||||
fontWeight: 100,
|
||||
fontSize: '10px'
|
||||
}}>{moment.unix(comment.creationDate!).fromNow()}</span>
|
||||
</div>
|
||||
)
|
||||
const commentBody = (
|
||||
<p style={this.styles.commentBody as any}>{comment.text}</p>
|
||||
)
|
||||
|
||||
const {userId} = comment
|
||||
|
||||
return (
|
||||
<div className='animate-top' style={this.styles.comment} key={comment.id!}>
|
||||
<Paper zDepth={0} className='animate2-top10' style={{ position: 'relative', padding: '', display: (!this.state.display ? 'block' : 'none') }}>
|
||||
<div style={{ marginLeft: '0px', padding: '16px 56px 0px 72px', position: 'relative' }}>
|
||||
<NavLink to={`/${userId}`}><UserAvatarComponent fullName={this.props.fullName} fileName={this.props.avatar} style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', position: 'absolute', top: '8px', left: '16px' }} size={36} /></NavLink>
|
||||
<NavLink to={`/${userId}`}> <Author /></NavLink>
|
||||
{(!this.props.isCommentOwner && !this.props.isPostOwner && this.props.disableComments ) ? '' : (<RightIconMenu />)}
|
||||
<div style={{ outline: 'none', marginLeft: '16px', flex: 'auto', flexGrow: 1 }}>
|
||||
<textarea ref={this.textareaRef} className='animate2-top10' style={ Object.assign({}, this.styles.textarea, { display: (this.props.comment.editorStatus ? 'block' : 'none') })} onChange={this.handleOnChange} value={this.state.text!}></textarea>
|
||||
<Linkify properties={{target: '_blank', style: {color: 'blue'}}}>
|
||||
<div ref={this.divCommentRef} className='animate2-top10' style={{ fontWeight: 100, fontSize: '14px', height: '100%', border: 'none', width: '100%', outline: 'none', resize: 'none', display: (!this.props.comment.editorStatus ? 'block' : 'none') }}>{this.state.text}</div>
|
||||
</Linkify>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: (this.props.comment.editorStatus ? 'flex' : 'none'), flexDirection: 'row-reverse' }}>
|
||||
<FlatButton primary={true} disabled={this.state.editDisabled} label='Update' style={{ float: 'right', clear: 'both', zIndex: 5, margin: '0px 5px 5px 0px', fontWeight: 400 }} onClick={this.handleUpdateComment} />
|
||||
<FlatButton primary={true} label='Cancel' style={this.styles.cancel as any} onClick={this.handleCancelEdit} />
|
||||
</div>
|
||||
</Paper>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: any) => {
|
||||
return {
|
||||
delete: (id: string| null, postId: string) => dispatch(commentActions.dbDeleteComment(id, postId)),
|
||||
update: (id: string, postId: string, comment: string) => dispatch(commentActions.dbUpdateComment(id, postId, comment)),
|
||||
openEditor: () => dispatch(commentActions.openCommentEditor({ id: ownProps.comment.id, postId: ownProps.comment.postId })),
|
||||
closeEditor: () => dispatch(commentActions.closeCommentEditor({ id: ownProps.comment.id, postId: ownProps.comment.postId })),
|
||||
getUserInfo: () => dispatch(userActions.dbGetUserInfoByUserId(ownProps.comment.userId,''))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: any) => {
|
||||
const {uid} = state.authorize
|
||||
const avatar = state.user.info && state.user.info[ownProps.comment.userId] ? state.user.info[ownProps.comment.userId].avatar || '' : ''
|
||||
const fullName = state.user.info && state.user.info[ownProps.comment.userId] ? state.user.info[ownProps.comment.userId].fullName || '' : ''
|
||||
return {
|
||||
uid: uid,
|
||||
isCommentOwner: (uid === ownProps.comment.userId),
|
||||
info: state.user.info,
|
||||
avatar,
|
||||
fullName
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CommentComponent as any)
|
||||
98
src/components/comment/ICommentComponentProps.ts
Normal file
98
src/components/comment/ICommentComponentProps.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Comment } from 'core/domain/comments'
|
||||
import { Profile } from 'core/domain/users'
|
||||
export interface ICommentComponentProps {
|
||||
|
||||
/**
|
||||
* Comment
|
||||
*
|
||||
* @type {Comment}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
comment: Comment
|
||||
|
||||
/**
|
||||
* Open profile editor
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
openEditor: Function
|
||||
|
||||
/**
|
||||
* Close comment editor
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
closeEditor: () => any
|
||||
|
||||
/**
|
||||
* Current user is comment owner {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
isCommentOwner: boolean
|
||||
|
||||
/**
|
||||
* Current user is post owner {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
isPostOwner: boolean
|
||||
|
||||
/**
|
||||
* Update comment
|
||||
*
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
update: (id?: string | null, postId?: string, comment?: string | null) => any
|
||||
|
||||
/**
|
||||
* Delete comment
|
||||
*
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
delete: (id?: string| null, postId?: string) => any
|
||||
|
||||
/**
|
||||
* Get user profile
|
||||
*
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
getUserInfo: () => void
|
||||
|
||||
/**
|
||||
* User profile
|
||||
*
|
||||
* @type {{[userId: string]: Profile}}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
info: {[userId: string]: Profile}
|
||||
|
||||
/**
|
||||
* User full name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
fullName: string
|
||||
|
||||
/**
|
||||
* User avatar address
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof Comment
|
||||
*/
|
||||
avatar: string
|
||||
|
||||
/**
|
||||
* Writing comment on the post is disabled {true} or not false
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
disableComments: boolean
|
||||
|
||||
}
|
||||
43
src/components/comment/ICommentComponentState.ts
Normal file
43
src/components/comment/ICommentComponentState.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
export interface ICommentComponentState {
|
||||
|
||||
/**
|
||||
* Initialt text comment
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
initialText?: string | null
|
||||
|
||||
/**
|
||||
* Initialt text comment
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
text?: string | null
|
||||
|
||||
/**
|
||||
* Comment is in edit state {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentComponentState
|
||||
*/
|
||||
editDisabled: boolean
|
||||
|
||||
/**
|
||||
* Current user is the post owner {true} or not falses
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentComponentState
|
||||
*/
|
||||
isPostOwner: boolean
|
||||
|
||||
/**
|
||||
* Display comment {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentComponentState
|
||||
*/
|
||||
display?: boolean
|
||||
}
|
||||
2
src/components/comment/index.ts
Normal file
2
src/components/comment/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import CommentComponent from './CommentComponent'
|
||||
export default CommentComponent
|
||||
279
src/components/commentGroup/CommentGroupComponent.tsx
Normal file
279
src/components/commentGroup/CommentGroupComponent.tsx
Normal file
@@ -0,0 +1,279 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import Paper from 'material-ui/Paper'
|
||||
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 actions
|
||||
import * as commentActions from 'actions/commentActions'
|
||||
|
||||
// - Import app components
|
||||
import CommentListComponent from 'components/CommentList'
|
||||
import UserAvatarComponent from 'components/userAvatar'
|
||||
|
||||
import { ICommentGroupComponentProps } from './ICommentGroupComponentProps'
|
||||
import { ICommentGroupComponentState } from './ICommentGroupComponentState'
|
||||
import { Comment } from 'core/domain/comments/comment'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class CommentGroupComponent extends Component<ICommentGroupComponentProps, ICommentGroupComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
/**
|
||||
* If it's true comment box will be open
|
||||
*/
|
||||
open: PropTypes.bool,
|
||||
/**
|
||||
* If it's true the comment is disable to write
|
||||
*/
|
||||
disableComments: PropTypes.bool,
|
||||
/**
|
||||
* The post identifier which comment belong to
|
||||
*/
|
||||
postId: PropTypes.string,
|
||||
/**
|
||||
* If it's true the post owner is the logged in user which this post be long to the comment
|
||||
*/
|
||||
isPostOwner: PropTypes.bool,
|
||||
/**
|
||||
* Toggle on show/hide comment by passing callback from parent component
|
||||
*/
|
||||
onToggleRequest: PropTypes.func,
|
||||
/**
|
||||
* The user identifier of the post owner which comment belong to
|
||||
*/
|
||||
ownerPostUserId: PropTypes.string
|
||||
|
||||
}
|
||||
|
||||
styles = {
|
||||
commentItem: {
|
||||
height: '60px',
|
||||
position: '',
|
||||
zIndex: ''
|
||||
},
|
||||
toggleShowList: {
|
||||
height: '60px',
|
||||
zIndex: 5
|
||||
},
|
||||
writeCommentTextField: {
|
||||
width: '100%'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: ICommentGroupComponentProps) {
|
||||
super(props)
|
||||
|
||||
/**
|
||||
* Defaul state
|
||||
*/
|
||||
this.state = {
|
||||
commentText: '',
|
||||
postDisable: true
|
||||
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
this.commentList = this.commentList.bind(this)
|
||||
this.handlePostComment = this.handlePostComment.bind(this)
|
||||
this.clearCommentWrite = this.clearCommentWrite.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear comment text field
|
||||
*/
|
||||
clearCommentWrite = () => {
|
||||
this.setState({
|
||||
commentText: '',
|
||||
postDisable: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Post comment
|
||||
*/
|
||||
handlePostComment = () => {
|
||||
this.props.send!(this.state.commentText, this.props.postId, this.clearCommentWrite)
|
||||
}
|
||||
|
||||
/**
|
||||
* When comment text changed
|
||||
* @param {event} evt is an event passed by change comment text callback funciton
|
||||
* @param {string} data is the comment text which user writes
|
||||
*/
|
||||
handleOnChange = (evt: any, data: any) => {
|
||||
this.setState({ commentText: data })
|
||||
if (data.length === 0 || data.trim() === '') {
|
||||
this.setState({
|
||||
commentText: '',
|
||||
postDisable: true
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
commentText: data,
|
||||
postDisable: false
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comments' DOM
|
||||
* @return {DOM} list of comments' DOM
|
||||
*/
|
||||
commentList = () => {
|
||||
let comments = this.props.comments
|
||||
if (comments) {
|
||||
|
||||
let parsedComments: Comment[] = []
|
||||
Object.keys(comments).slice(0, 3).forEach((commentId) => {
|
||||
parsedComments.push({
|
||||
id: commentId,
|
||||
...comments![commentId]
|
||||
})
|
||||
})
|
||||
if (parsedComments.length === 2) {
|
||||
parsedComments.push(parsedComments[0])
|
||||
} else if (parsedComments.length === 1) {
|
||||
parsedComments.push(parsedComments[0])
|
||||
parsedComments.push(parsedComments[0])
|
||||
}
|
||||
return parsedComments.map((comment, index) => {
|
||||
const {userInfo} = this.props
|
||||
|
||||
const commentAvatar = userInfo && userInfo[comment.userId!] ? userInfo[comment.userId!].avatar || '' : ''
|
||||
const commentFullName = userInfo && userInfo[comment.userId!] ? userInfo[comment.userId!].fullName || '' : ''
|
||||
|
||||
return (<ListItem key={index} style={this.styles.commentItem as any} innerDivStyle={{ padding: '6px 16px 16px 72px' }}
|
||||
leftAvatar={<UserAvatarComponent fullName={commentFullName} fileName={commentAvatar} style={{ top: '8px' }} size={36} />}
|
||||
secondaryText={<div style={{ height: '' }}>
|
||||
<span style={{
|
||||
fontSize: '13px',
|
||||
paddingRight: '10px',
|
||||
fontWeight: 400,
|
||||
color: 'rgba(0,0,0,0.87)',
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden'
|
||||
}}>
|
||||
{comment.userDisplayName}:
|
||||
</span>
|
||||
<span style={{
|
||||
fontSize: '13px',
|
||||
lineHeight: '20px',
|
||||
color: 'rgba(0,0,0,0.87)',
|
||||
fontWeight: 300,
|
||||
whiteSpace: 'pre-wrap'
|
||||
}}>{comment.text}</span>
|
||||
</div>}
|
||||
secondaryTextLines={2}
|
||||
/>
|
||||
|
||||
)
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
<div style={this.props.comments && Object.keys(this.props.comments).length > 0 ? { display: 'block' } : { display: 'none' }}>
|
||||
<Divider />
|
||||
<Paper zDepth={0} className='animate-top' style={!this.props.open ? { display: 'block' } : { display: 'none' }}>
|
||||
|
||||
<div style={{ position: 'relative', height: '60px' }} >
|
||||
<FlatButton label=' ' style={this.styles.toggleShowList} fullWidth={true} onClick={this.props.onToggleRequest} />
|
||||
|
||||
<div className='comment__list-show'>
|
||||
{this.commentList()}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Paper>
|
||||
{(this.props.comments && Object.keys(this.props.comments).length > 0)
|
||||
? (<Paper zDepth={0} style={this.props.open ? { display: 'block', padding: '0px 0px' } : { display: 'none', padding: '12px 16px' }}>
|
||||
<CommentListComponent comments={this.props.comments} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/>
|
||||
|
||||
</Paper>) : ''}
|
||||
|
||||
</div>
|
||||
{!this.props.disableComments ? (<div>
|
||||
<Divider />
|
||||
<Paper zDepth={0} className='animate2-top10' style={{ position: 'relative', overflowY: 'auto', padding: '12px 16px', display: (this.props.open ? 'block' : 'none') }}>
|
||||
|
||||
<div style={{ display: 'flex' }}>
|
||||
<UserAvatarComponent fullName={this.props.fullName!} fileName={this.props.avatar!} style={{ flex: 'none', margin: '4px 0px' }} size={36} />
|
||||
<div style={{ outline: 'none', marginLeft: '16px', flex: 'auto', flexGrow: 1 }}>
|
||||
<TextField
|
||||
value={this.state.commentText}
|
||||
onChange={this.handleOnChange}
|
||||
hintText='Add a comment...'
|
||||
underlineShow={false}
|
||||
multiLine={true}
|
||||
rows={1}
|
||||
hintStyle={{ fontWeight: 100, fontSize: '14px' }}
|
||||
rowsMax={4}
|
||||
textareaStyle={{ fontWeight: 100, fontSize: '14px' }}
|
||||
style={this.styles.writeCommentTextField}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<FlatButton primary={true} disabled={this.state.postDisable} label='Post' style={{ float: 'right', clear: 'both', zIndex: 5, margin: '0px 5px 5px 0px', fontWeight: 400 }} onClick={this.handlePostComment} />
|
||||
</Paper>
|
||||
</div>) : ''}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: ICommentGroupComponentProps) => {
|
||||
return {
|
||||
send: (text: string, postId: string, callBack: Function) => {
|
||||
dispatch(commentActions.dbAddComment(ownProps.ownerPostUserId,{
|
||||
postId: postId,
|
||||
text: text
|
||||
}, callBack))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: ICommentGroupComponentProps) => {
|
||||
return {
|
||||
comments: state.comment.postComments[ownProps.postId],
|
||||
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar || '' : '',
|
||||
fullName: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].fullName || '' : '',
|
||||
userInfo: state.user.info
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CommentGroupComponent as any)
|
||||
93
src/components/commentGroup/ICommentGroupComponentProps.ts
Normal file
93
src/components/commentGroup/ICommentGroupComponentProps.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { Profile } from 'core/domain/users'
|
||||
import { Comment } from 'core/domain/comments'
|
||||
export interface ICommentGroupComponentProps {
|
||||
|
||||
/**
|
||||
* Commnets
|
||||
*
|
||||
* @type {{[commentId: string]: Comment}}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
comments?: {[commentId: string]: Comment}
|
||||
|
||||
/**
|
||||
* The post identifier which comment belong to
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
postId: string
|
||||
|
||||
/**
|
||||
* Users` profile
|
||||
*
|
||||
* @type {{[userId: string]: Profile}}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
userInfo?: {[userId: string]: Profile}
|
||||
|
||||
/**
|
||||
* Comment group is open {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
open: boolean
|
||||
|
||||
/**
|
||||
* Comment is disabled {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
disableComments: boolean
|
||||
|
||||
/**
|
||||
* Current user is the post owner {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
isPostOwner: boolean
|
||||
|
||||
/**
|
||||
* User full name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
fullName?: string
|
||||
|
||||
/**
|
||||
* Avatar URL address
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
avatar?: string
|
||||
|
||||
/**
|
||||
* Toggle comment list open/close
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
onToggleRequest: () => void
|
||||
|
||||
/**
|
||||
* The identifier of post owner
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
ownerPostUserId: string
|
||||
|
||||
/**
|
||||
* Send comment
|
||||
*
|
||||
* @type {(commentText: string, postId: string, clearCommentWrite: Function)}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
send?: (commentText: string, postId: string, clearCommentWrite: () => void) => any
|
||||
|
||||
}
|
||||
19
src/components/commentGroup/ICommentGroupComponentState.ts
Normal file
19
src/components/commentGroup/ICommentGroupComponentState.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
|
||||
export interface ICommentGroupComponentState {
|
||||
|
||||
/**
|
||||
* Comment text
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICommentGroupComponentState
|
||||
*/
|
||||
commentText: string
|
||||
|
||||
/**
|
||||
* Disable post comment
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentGroupComponentState
|
||||
*/
|
||||
postDisable: boolean
|
||||
}
|
||||
2
src/components/commentGroup/index.ts
Normal file
2
src/components/commentGroup/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import CommentGroupComponent from './CommentGroupComponent'
|
||||
export default CommentGroupComponent
|
||||
127
src/components/commentList/CommentListComponent.tsx
Normal file
127
src/components/commentList/CommentListComponent.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { List, ListItem } from 'material-ui/List'
|
||||
|
||||
// - Import app components
|
||||
import CommentComponent from 'components/Comment'
|
||||
|
||||
import * as PostAPI from 'api/PostAPI'
|
||||
|
||||
import { ICommentListComponentProps } from './ICommentListComponentProps'
|
||||
import { ICommentListComponentState } from './ICommentListComponentState'
|
||||
import { Comment } from 'core/domain/comments'
|
||||
|
||||
// - Import actions
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class CommentListComponent extends Component<ICommentListComponentProps, ICommentListComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
/**
|
||||
* If it's true the post owner is the logged in user which this post be long to the comment
|
||||
*/
|
||||
isPostOwner: PropTypes.bool,
|
||||
/**
|
||||
* If it's true the comment is disable to write
|
||||
*/
|
||||
disableComments: PropTypes.bool
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: ICommentListComponentProps) {
|
||||
super(props)
|
||||
|
||||
/**
|
||||
* Default state
|
||||
*/
|
||||
this.state = {
|
||||
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get comments' DOM
|
||||
* @return {DOM} list of comments' DOM
|
||||
*/
|
||||
commentList = () => {
|
||||
let comments = this.props.comments
|
||||
if (comments) {
|
||||
|
||||
let parsedComments: Comment[] = []
|
||||
Object.keys(comments).forEach((commentId) => {
|
||||
parsedComments.push({
|
||||
id: commentId,
|
||||
...comments[commentId]
|
||||
})
|
||||
})
|
||||
let sortedComments = PostAPI.sortObjectsDate(parsedComments)
|
||||
|
||||
return sortedComments.map((comment: Comment, index: number, array: Comment) => {
|
||||
|
||||
return <CommentComponent key={comment.id} comment={comment} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/>
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
|
||||
const styles: any = {
|
||||
list: {
|
||||
width: '100%',
|
||||
maxHeight: 450,
|
||||
overflowY: 'auto'
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
<List style={styles.list}>
|
||||
|
||||
{this.commentList()}
|
||||
</List>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: ICommentListComponentProps) => {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any) => {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(CommentListComponent as any)
|
||||
28
src/components/commentList/ICommentListComponentProps.ts
Normal file
28
src/components/commentList/ICommentListComponentProps.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Comment } from 'core/domain/comments'
|
||||
|
||||
export interface ICommentListComponentProps {
|
||||
|
||||
/**
|
||||
* Ad dictionary of comment
|
||||
*
|
||||
* @type {{[commentId: string]: Comment}}
|
||||
* @memberof ICommentListComponentProps
|
||||
*/
|
||||
comments: {[commentId: string]: Comment}
|
||||
|
||||
/**
|
||||
* Current user is post the post owner {true} or not false
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentListComponentProps
|
||||
*/
|
||||
isPostOwner: boolean
|
||||
|
||||
/**
|
||||
* Comment on the post is disabled {false} or not {true}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof ICommentListComponentProps
|
||||
*/
|
||||
disableComments: boolean
|
||||
}
|
||||
4
src/components/commentList/ICommentListComponentState.ts
Normal file
4
src/components/commentList/ICommentListComponentState.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface ICommentListComponentState {
|
||||
|
||||
}
|
||||
2
src/components/commentList/index.ts
Normal file
2
src/components/commentList/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import CommentListComponent from './CommentListComponent'
|
||||
export default CommentListComponent
|
||||
435
src/components/editProfile/EditProfileComponent.tsx
Normal file
435
src/components/editProfile/EditProfileComponent.tsx
Normal file
@@ -0,0 +1,435 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { grey400, darkBlack, lightBlack } from 'material-ui/styles/colors'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'
|
||||
import SvgCamera from 'material-ui/svg-icons/image/photo-camera'
|
||||
import IconMenu from 'material-ui/IconMenu'
|
||||
import MenuItem from 'material-ui/MenuItem'
|
||||
import FlatButton from 'material-ui/FlatButton'
|
||||
import RaisedButton from 'material-ui/RaisedButton'
|
||||
import EventListener, { withOptions } from 'react-event-listener'
|
||||
import Dialog from 'material-ui/Dialog'
|
||||
import Divider from 'material-ui/Divider'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import TextField from 'material-ui/TextField'
|
||||
|
||||
// - Import app components
|
||||
import ImgCover from 'components/imgCover'
|
||||
import UserAvatarComponent from 'components/userAvatar'
|
||||
import ImageGallery from 'components/imageGallery'
|
||||
import DialogTitle from 'layouts/DialogTitle'
|
||||
|
||||
// - Import API
|
||||
import FileAPI from 'api/FileAPI'
|
||||
|
||||
// - Import actions
|
||||
import * as userActions from 'actions/userActions'
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as imageGalleryActions from 'actions/imageGalleryActions'
|
||||
|
||||
import { IEditProfileComponentProps } from './IEditProfileComponentProps'
|
||||
import { IEditProfileComponentState } from './IEditProfileComponentState'
|
||||
import { Profile } from 'core/domain/users'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class EditProfileComponent extends Component<IEditProfileComponentProps,IEditProfileComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
|
||||
/**
|
||||
* User avatar address
|
||||
*/
|
||||
avatar: PropTypes.string,
|
||||
/**
|
||||
* User avatar address
|
||||
*/
|
||||
banner: PropTypes.string,
|
||||
/**
|
||||
* User full name
|
||||
*/
|
||||
fullName: PropTypes.string.isRequired
|
||||
|
||||
}
|
||||
|
||||
styles = {
|
||||
avatar: {
|
||||
border: '2px solid rgb(255, 255, 255)'
|
||||
},
|
||||
paper: {
|
||||
width: '90%',
|
||||
height: '100%',
|
||||
margin: '0 auto',
|
||||
display: 'block'
|
||||
},
|
||||
title: {
|
||||
padding: '24px 24px 20px 24px',
|
||||
font: '500 20px Roboto,RobotoDraft,Helvetica,Arial,sans-serif',
|
||||
display: 'flex',
|
||||
wordWrap: 'break-word',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
overflow: 'hidden',
|
||||
flexGrow: 1
|
||||
},
|
||||
actions: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
padding: '24px 24px 20px'
|
||||
},
|
||||
updateButton: {
|
||||
marginLeft: '10px'
|
||||
},
|
||||
box: {
|
||||
padding: '0px 24px 20px 24px',
|
||||
display: 'flex'
|
||||
|
||||
},
|
||||
dialogGallery: {
|
||||
width: '',
|
||||
maxWidth: '530px',
|
||||
borderRadius: '4px'
|
||||
},
|
||||
iconButtonSmall: {
|
||||
},
|
||||
iconButton: {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: IEditProfileComponentProps) {
|
||||
super(props)
|
||||
// Defaul state
|
||||
this.state = {
|
||||
/**
|
||||
* If it's true the winow is in small size
|
||||
*/
|
||||
isSmall: false,
|
||||
/**
|
||||
* User tag line input value
|
||||
*/
|
||||
tagLineInput: props.info!.tagLine || '',
|
||||
/**
|
||||
* User full name input value
|
||||
*/
|
||||
fullNameInput: props.info!.fullName || '',
|
||||
/**
|
||||
* Error message of full name input
|
||||
*/
|
||||
fullNameInputError: '',
|
||||
/**
|
||||
* User banner address
|
||||
*/
|
||||
banner: props.banner || 'https://firebasestorage.googleapis.com/v0/b/open-social-33d92.appspot.com/o/images%2F751145a1-9488-46fd-a97e-04018665a6d3.JPG?alt=media&token=1a1d5e21-5101-450e-9054-ea4a20e06c57',
|
||||
/**
|
||||
* User avatar address
|
||||
*/
|
||||
avatar: props.avatar || '',
|
||||
/**
|
||||
* It's true if the image galley for banner is open
|
||||
*/
|
||||
openBanner: false,
|
||||
/**
|
||||
* It's true if the image gallery for avatar is open
|
||||
*/
|
||||
openAvatar: false
|
||||
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
this.handleUpdate = this.handleUpdate.bind(this)
|
||||
this.handleRequestSetAvatar = this.handleRequestSetAvatar.bind(this)
|
||||
this.handleRequestSetBanner = this.handleRequestSetBanner.bind(this)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Close image gallery of banner
|
||||
*/
|
||||
handleCloseBannerGallery = () => {
|
||||
this.setState({
|
||||
openBanner: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Open image gallery of banner
|
||||
*/
|
||||
handleOpenBannerGallery = () => {
|
||||
this.setState({
|
||||
openBanner: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Close image gallery of avatar
|
||||
*/
|
||||
handleCloseAvatarGallery = () => {
|
||||
this.setState({
|
||||
openAvatar: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Open image gallery of avatar
|
||||
*/
|
||||
handleOpenAvatarGallery = () => {
|
||||
this.setState({
|
||||
openAvatar: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Set banner image url
|
||||
*/
|
||||
handleRequestSetBanner = (url: string) => {
|
||||
this.setState({
|
||||
banner: url
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Set avatar image url
|
||||
*/
|
||||
handleRequestSetAvatar = (fileName: string) => {
|
||||
this.setState({
|
||||
avatar: fileName
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Update profile on the server
|
||||
*
|
||||
*
|
||||
* @memberof EditProfile
|
||||
*/
|
||||
handleUpdate = () => {
|
||||
const {fullNameInput, tagLineInput, avatar, banner} = this.state
|
||||
|
||||
if (this.state.fullNameInput.trim() === '') {
|
||||
this.setState({
|
||||
fullNameInputError: 'This field is required'
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
fullNameInputError: ''
|
||||
})
|
||||
|
||||
this.props.update!({
|
||||
fullName: fullNameInput,
|
||||
tagLine: tagLineInput,
|
||||
avatar: avatar,
|
||||
banner: banner
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle data on input change
|
||||
* @param {event} evt is an event of inputs of element on change
|
||||
*/
|
||||
handleInputChange = (event: any) => {
|
||||
const target = event.target
|
||||
const value = target.type === 'checkbox' ? target.checked : target.value
|
||||
const name = target.name
|
||||
this.setState({
|
||||
[name]: value
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle resize event for window to change sidebar status
|
||||
* @param {any} event is the event is passed by winodw resize event
|
||||
*/
|
||||
handleResize = (event: any) => {
|
||||
|
||||
// Set initial state
|
||||
let width = window.innerWidth
|
||||
|
||||
if (width > 900) {
|
||||
this.setState({
|
||||
isSmall: false
|
||||
})
|
||||
|
||||
} else {
|
||||
this.setState({
|
||||
isSmall: true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.handleResize(null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
|
||||
const iconButtonElement = (
|
||||
<IconButton style={this.state.isSmall ? this.styles.iconButtonSmall : this.styles.iconButton} iconStyle={this.state.isSmall ? this.styles.iconButtonSmall : this.styles.iconButton}
|
||||
touch={true}
|
||||
>
|
||||
<MoreVertIcon color={grey400} viewBox='10 0 24 24' />
|
||||
</IconButton>
|
||||
)
|
||||
|
||||
const RightIconMenu = () => (
|
||||
<IconMenu iconButtonElement={iconButtonElement}>
|
||||
<MenuItem style={{ fontSize: '14px' }}>Reply</MenuItem>
|
||||
<MenuItem style={{ fontSize: '14px' }}>Edit</MenuItem>
|
||||
<MenuItem style={{ fontSize: '14px' }}>Delete</MenuItem>
|
||||
</IconMenu>
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
<div>
|
||||
{/* Edit profile dialog */}
|
||||
<Dialog
|
||||
key='Edit-Profile'
|
||||
modal={false}
|
||||
open={this.props.open!}
|
||||
onRequestClose={this.props.onRequestClose}
|
||||
autoScrollBodyContent={true}
|
||||
bodyStyle={{ backgroundColor: 'none', padding: 'none', borderTop: 'none', borderBottom: 'none' }}
|
||||
overlayStyle={{ background: 'rgba(0,0,0,0.12)' }}
|
||||
contentStyle={{ backgroundColor: 'none', maxWidth: '450px', maxHeight: 'none', height: '100%' }}
|
||||
style={{ backgroundColor: 'none', maxHeight: 'none', height: '100%' }}
|
||||
>
|
||||
{/* Banner */}
|
||||
<div style={{ position: 'relative' }}>
|
||||
<ImgCover width='100%' height='250px' borderRadius='2px' fileName={this.state.banner} />
|
||||
<div className='g__circle-black' onClick={this.handleOpenBannerGallery} style={{ position: 'absolute', right: '10px', top: '10px' }}>
|
||||
<SvgCamera style={{ fill: 'rgba(255, 255, 255, 0.88)', transform: 'translate(6px, 6px)' }} />
|
||||
</div>
|
||||
</div>
|
||||
<div className='profile__edit'>
|
||||
<EventListener
|
||||
target='window'
|
||||
onResize={this.handleResize}
|
||||
/>
|
||||
<div className='left'>
|
||||
<div style={{ display: 'flex', justifyContent: 'center' }}>
|
||||
{/* Avatar */}
|
||||
<div className='g__circle-black' onClick={this.handleOpenAvatarGallery} style={{ position: 'absolute', left: '50%', display: 'inline-block', top: '52px', margin: '-18px' }}>
|
||||
<SvgCamera style={{ fill: 'rgba(255, 255, 255, 0.88)', transform: 'translate(6px, 6px)' }} />
|
||||
|
||||
</div>
|
||||
<UserAvatarComponent fullName={(this.props.info ? this.props.info.fullName : '')} fileName={this.state.avatar} size={90} style={this.styles.avatar} />
|
||||
</div>
|
||||
<div className='info'>
|
||||
<div className='fullName'>
|
||||
{this.props.fullName}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* Edit user information box*/}
|
||||
<Paper style={this.styles.paper} zDepth={1}>
|
||||
<div style={this.styles.title as any}>Personal Information</div>
|
||||
<div style={this.styles.box}>
|
||||
<TextField
|
||||
floatingLabelText='Full name'
|
||||
onChange={this.handleInputChange}
|
||||
name='fullNameInput'
|
||||
errorText={this.state.fullNameInputError}
|
||||
value={this.state.fullNameInput}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<div style={this.styles.box}>
|
||||
<TextField
|
||||
floatingLabelText='Tag Line'
|
||||
onChange={this.handleInputChange}
|
||||
name='tagLineInput'
|
||||
value={this.state.tagLineInput}
|
||||
/>
|
||||
</div>
|
||||
<br />
|
||||
<div style={this.styles.actions as any}>
|
||||
<FlatButton label='CANCEL' onClick={this.props.onRequestClose} />
|
||||
<RaisedButton label='UPDATE' primary={true} onClick={this.handleUpdate} style={this.styles.updateButton} />
|
||||
</div>
|
||||
</Paper>
|
||||
<div style={{ height: '16px' }}></div>
|
||||
|
||||
</Dialog>
|
||||
|
||||
{/* Image gallery for banner*/}
|
||||
<Dialog
|
||||
title={<DialogTitle title='Choose an banner image' onRequestClose={this.handleCloseBannerGallery} />}
|
||||
modal={false}
|
||||
open={this.state.openBanner}
|
||||
contentStyle={this.styles.dialogGallery}
|
||||
onRequestClose={this.handleCloseBannerGallery}
|
||||
overlayStyle={{ background: 'rgba(0,0,0,0.12)' }}
|
||||
autoDetectWindowHeight={false}
|
||||
|
||||
>
|
||||
<ImageGallery set={this.handleRequestSetBanner} close={this.handleCloseBannerGallery} />
|
||||
</Dialog>
|
||||
|
||||
{/* Image gallery for avatar */}
|
||||
<Dialog
|
||||
title={<DialogTitle title='Choose an avatar image' onRequestClose={this.handleCloseAvatarGallery} />}
|
||||
modal={false}
|
||||
open={this.state.openAvatar}
|
||||
contentStyle={this.styles.dialogGallery}
|
||||
onRequestClose={this.handleCloseAvatarGallery}
|
||||
overlayStyle={{ background: 'rgba(0,0,0,0.12)' }}
|
||||
autoDetectWindowHeight={false}
|
||||
|
||||
>
|
||||
<ImageGallery set={this.handleRequestSetAvatar} close={this.handleCloseAvatarGallery} />
|
||||
</Dialog>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IEditProfileComponentProps) => {
|
||||
return {
|
||||
update: (info: Profile) => dispatch(userActions.dbUpdateUserInfo(info)),
|
||||
onRequestClose: () => dispatch(userActions.closeEditProfile())
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: IEditProfileComponentProps) => {
|
||||
return {
|
||||
open: state.user.openEditProfile,
|
||||
info: state.user.info[state.authorize.uid],
|
||||
avatarURL: state.imageGallery.imageURLList
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(EditProfileComponent as any)
|
||||
58
src/components/editProfile/IEditProfileComponentProps.ts
Normal file
58
src/components/editProfile/IEditProfileComponentProps.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Profile } from 'core/domain/users'
|
||||
|
||||
export interface IEditProfileComponentProps {
|
||||
|
||||
/**
|
||||
* User profile
|
||||
*
|
||||
* @type {Profile}
|
||||
* @memberof IEditProfileComponentProps
|
||||
*/
|
||||
info?: Profile
|
||||
|
||||
/**
|
||||
* User profile banner addresss
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentProps
|
||||
*/
|
||||
banner: string
|
||||
|
||||
/**
|
||||
* User avatar address
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentProps
|
||||
*/
|
||||
avatar: string
|
||||
|
||||
/**
|
||||
* User full name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentProps
|
||||
*/
|
||||
fullName: string
|
||||
|
||||
/**
|
||||
* Edit profile dialog is open {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IEditProfileComponentProps
|
||||
*/
|
||||
open?: boolean
|
||||
|
||||
/**
|
||||
* Update user profile
|
||||
*
|
||||
* @memberof IEditProfileComponentProps
|
||||
*/
|
||||
update?: (profile: Profile) => void
|
||||
|
||||
/**
|
||||
* On edit profile dialog close event
|
||||
*
|
||||
* @memberof IEditProfileComponentProps
|
||||
*/
|
||||
onRequestClose?: () => void
|
||||
}
|
||||
68
src/components/editProfile/IEditProfileComponentState.ts
Normal file
68
src/components/editProfile/IEditProfileComponentState.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
|
||||
export interface IEditProfileComponentState {
|
||||
|
||||
/**
|
||||
* Full name input value
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
fullNameInput: string
|
||||
|
||||
/**
|
||||
* Full name input error message
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
fullNameInputError: string
|
||||
|
||||
/**
|
||||
* Tag line input value
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
tagLineInput: string
|
||||
|
||||
/**
|
||||
* Edit profile page is small size {true} or big {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
isSmall: boolean
|
||||
|
||||
/**
|
||||
* User's banner URL
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
banner: string
|
||||
|
||||
/**
|
||||
* User's avatar URL address
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
avatar: string
|
||||
|
||||
/**
|
||||
* Image gallery dialog is open for choosing banner image {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
openBanner: boolean
|
||||
|
||||
/**
|
||||
* Image gallery dialog is open for choosing avatar image {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IEditProfileComponentState
|
||||
*/
|
||||
openAvatar: boolean
|
||||
|
||||
}
|
||||
2
src/components/editProfile/index.ts
Normal file
2
src/components/editProfile/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import EditProfileComponent from './EditProfileComponent'
|
||||
export default EditProfileComponent
|
||||
108
src/components/findPeople/FindPeopleComponent.tsx
Normal file
108
src/components/findPeople/FindPeopleComponent.tsx
Normal file
@@ -0,0 +1,108 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import Paper from 'material-ui/Paper'
|
||||
|
||||
// - Import app components
|
||||
import UserBoxList from 'components/userBoxList'
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as userActions from 'actions/userActions'
|
||||
import { IFindPeopleComponentProps } from './IFindPeopleComponentProps'
|
||||
import { IFindPeopleComponentState } from './IFindPeopleComponentState'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class FindPeopleComponent extends Component<IFindPeopleComponentProps, IFindPeopleComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: IFindPeopleComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Defaul state
|
||||
this.state = {
|
||||
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
this.props.loadPeople!()
|
||||
}
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
|
||||
const styles = {
|
||||
paper: {
|
||||
height: 254,
|
||||
width: 243,
|
||||
margin: 10,
|
||||
textAlign: 'center',
|
||||
maxWidth: '257px'
|
||||
},
|
||||
followButton: {
|
||||
position: 'absolute',
|
||||
bottom: '8px',
|
||||
left: 0,
|
||||
right: 0
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{this.props.peopleInfo && Object.keys(this.props.peopleInfo).length !== 0 ? (<div>
|
||||
<div className='profile__title'>
|
||||
Suggestions for you
|
||||
</div>
|
||||
<UserBoxList users={this.props.peopleInfo}/>
|
||||
<div style={{ height: '24px' }}></div>
|
||||
</div>) : (<div className='g__title-center'>
|
||||
Nothing to show! :(
|
||||
</div>)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IFindPeopleComponentProps) => {
|
||||
return {
|
||||
loadPeople: () => dispatch(userActions.dbGetPeopleInfo())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: IFindPeopleComponentProps) => {
|
||||
return {
|
||||
peopleInfo: state.user.info
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FindPeopleComponent as any)
|
||||
20
src/components/findPeople/IFindPeopleComponentProps.ts
Normal file
20
src/components/findPeople/IFindPeopleComponentProps.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Profile } from 'core/domain/users/profile'
|
||||
|
||||
export interface IFindPeopleComponentProps {
|
||||
|
||||
/**
|
||||
* Load users' profile
|
||||
*
|
||||
* @memberof IFindPeopleComponentProps
|
||||
*/
|
||||
loadPeople?: () => any
|
||||
|
||||
/**
|
||||
* Users' profile
|
||||
*
|
||||
* @type {{[userId: string]: Profile}}
|
||||
* @memberof IFindPeopleComponentProps
|
||||
*/
|
||||
peopleInfo?: {[userId: string]: Profile}
|
||||
|
||||
}
|
||||
4
src/components/findPeople/IFindPeopleComponentState.ts
Normal file
4
src/components/findPeople/IFindPeopleComponentState.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface IFindPeopleComponentState {
|
||||
|
||||
}
|
||||
2
src/components/findPeople/index.ts
Normal file
2
src/components/findPeople/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import FindPeopleComponent from './FindPeopleComponent'
|
||||
export default FindPeopleComponent
|
||||
90
src/components/followers/FollowersComponent.tsx
Normal file
90
src/components/followers/FollowersComponent.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
// - Import app components
|
||||
import UserBoxList from 'components/userBoxList'
|
||||
|
||||
import { IFollowersComponentProps } from './IFollowersComponentProps'
|
||||
import { IFollowersComponentState } from './IFollowersComponentState'
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class FollowersComponent extends Component<IFollowersComponentProps,IFollowersComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: IFollowersComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Defaul state
|
||||
this.state = {
|
||||
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
return (
|
||||
<div>
|
||||
{(this.props.followers && Object.keys(this.props.followers).length !== 0) ? (<div>
|
||||
<div className='profile__title'>
|
||||
Followers
|
||||
</div>
|
||||
<UserBoxList users={this.props.followers} />
|
||||
<div style={{ height: '24px' }}></div>
|
||||
</div>)
|
||||
: (<div className='g__title-center'>
|
||||
No followers!
|
||||
</div>)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any,ownProps: IFollowersComponentProps) => {
|
||||
return{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any,ownProps: IFollowersComponentProps) => {
|
||||
const { uid } = state.authorize
|
||||
const circles = state.circle ? state.circle.userCircles[uid] : {}
|
||||
return{
|
||||
followers: circles ? (circles['-Followers'] ? circles['-Followers'].users || {} : {}) : {}
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps,mapDispatchToProps)(FollowersComponent as any)
|
||||
12
src/components/followers/IFollowersComponentProps.ts
Normal file
12
src/components/followers/IFollowersComponentProps.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { UserFollower } from 'core/domain/circles'
|
||||
|
||||
export interface IFollowersComponentProps {
|
||||
|
||||
/**
|
||||
* User followers info
|
||||
*
|
||||
* @type {{[userId: string]: UserFollower}}
|
||||
* @memberof IFindPeopleComponentProps
|
||||
*/
|
||||
followers?: {[userId: string]: UserFollower}
|
||||
}
|
||||
4
src/components/followers/IFollowersComponentState.ts
Normal file
4
src/components/followers/IFollowersComponentState.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface IFollowersComponentState {
|
||||
|
||||
}
|
||||
2
src/components/followers/index.ts
Normal file
2
src/components/followers/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import FollowersComponent from './FollowersComponent'
|
||||
export default FollowersComponent
|
||||
95
src/components/following/FollowingComponent.tsx
Normal file
95
src/components/following/FollowingComponent.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
// - Import app components
|
||||
import UserBoxList from 'components/userBoxList'
|
||||
|
||||
// - Import API
|
||||
import CircleAPI from 'api/CircleAPI'
|
||||
import { IFollowingComponentProps } from './IFollowingComponentProps'
|
||||
import { IFollowingComponentState } from './IFollowingComponentState'
|
||||
|
||||
// - Import actions
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class FollowingComponent extends Component<IFollowingComponentProps,IFollowingComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: IFollowingComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Defaul state
|
||||
this.state = {
|
||||
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{(this.props.followingUsers && Object.keys(this.props.followingUsers).length !== 0 ) ? (<div>
|
||||
<div className='profile__title'>
|
||||
Following
|
||||
</div>
|
||||
<UserBoxList users={this.props.followingUsers} />
|
||||
<div style={{ height: '24px' }}></div>
|
||||
|
||||
</div>) : (<div className='g__title-center'>
|
||||
No following user!
|
||||
</div>)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any,ownProp: IFollowingComponentProps) => {
|
||||
return{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any,ownProps: IFollowingComponentProps) => {
|
||||
const { uid } = state.authorize
|
||||
const circles = state.circle ? state.circle.userCircles[uid] : {}
|
||||
const followingUsers = CircleAPI.getFollowingUsers(circles)
|
||||
return {
|
||||
uid,
|
||||
circles,
|
||||
followingUsers
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps,mapDispatchToProps)(FollowingComponent as any)
|
||||
6
src/components/following/IFollowingComponentProps.ts
Normal file
6
src/components/following/IFollowingComponentProps.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { UserFollower } from 'core/domain/circles'
|
||||
|
||||
export interface IFollowingComponentProps {
|
||||
|
||||
followingUsers?: {[userId: string]: UserFollower}
|
||||
}
|
||||
4
src/components/following/IFollowingComponentState.ts
Normal file
4
src/components/following/IFollowingComponentState.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface IFollowingComponentState {
|
||||
|
||||
}
|
||||
2
src/components/following/index.ts
Normal file
2
src/components/following/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import FollowingComponent from './FollowingComponent'
|
||||
export default FollowingComponent
|
||||
200
src/components/home/HomeComponent.tsx
Normal file
200
src/components/home/HomeComponent.tsx
Normal file
@@ -0,0 +1,200 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import _ from 'lodash'
|
||||
import { Route, Switch, withRouter, Redirect, NavLink } from 'react-router-dom'
|
||||
import { connect } from 'react-redux'
|
||||
import { push } from 'react-router-redux'
|
||||
import Menu from 'material-ui/Menu'
|
||||
import MenuItem from 'material-ui/MenuItem'
|
||||
import Divider from 'material-ui/Divider'
|
||||
import SvgArrowLeft from 'material-ui/svg-icons/hardware/keyboard-arrow-left'
|
||||
import SvgHome from 'material-ui/svg-icons/action/home'
|
||||
import SvgFeedback from 'material-ui/svg-icons/action/feedback'
|
||||
import SvgSettings from 'material-ui/svg-icons/action/settings'
|
||||
import SvgAccountCircle from 'material-ui/svg-icons/action/account-circle'
|
||||
import SvgPeople from 'material-ui/svg-icons/social/people'
|
||||
|
||||
// - Import app components
|
||||
import Sidebar from 'components/sidebar'
|
||||
import StreamComponent from 'components/stream'
|
||||
import HomeHeader from 'components/homeHeader'
|
||||
import SidebarContent from 'components/sidebarContent'
|
||||
import SidebarMain from 'components/sidebarMain'
|
||||
import Profile from 'components/profile'
|
||||
import PostPage from 'components/postPage'
|
||||
import People from 'components/people'
|
||||
|
||||
// - Import API
|
||||
import CircleAPI from 'api/CircleAPI'
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
|
||||
import { IHomeComponentProps } from './IHomeComponentProps'
|
||||
import { IHomeComponentState } from './IHomeComponentState'
|
||||
|
||||
// - Create Home component class
|
||||
export class HomeComponent extends Component<IHomeComponentProps, IHomeComponentState> {
|
||||
|
||||
// Constructor
|
||||
constructor (props: IHomeComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Default state
|
||||
this.state = {
|
||||
sidebarOpen: () => _,
|
||||
sidebarStatus: true,
|
||||
sidebarOverlay: false
|
||||
}
|
||||
|
||||
// Binding function to `this`
|
||||
this.sidebar = this.sidebar.bind(this)
|
||||
this.sidebarStatus = this.sidebarStatus.bind(this)
|
||||
this.sidebarOverlay = this.sidebarOverlay.bind(this)
|
||||
this.handleCloseSidebar = this.handleCloseSidebar.bind(this)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* handle close sidebar
|
||||
*/
|
||||
handleCloseSidebar = () => {
|
||||
this.state.sidebarOpen!(false, 'overlay')
|
||||
}
|
||||
|
||||
/**
|
||||
* Change sidebar overlay status
|
||||
* @param {boolean} status if is true, the sidebar is on overlay status
|
||||
*/
|
||||
sidebarOverlay = (status: boolean) => {
|
||||
this.setState({
|
||||
sidebarOverlay: status
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass the function to change sidebar status
|
||||
* @param {boolean} open is a function callback to change sidebar status out of sidebar component
|
||||
*/
|
||||
sidebar = (open: (status: boolean, source: string) => void) => {
|
||||
|
||||
this.setState({
|
||||
sidebarOpen: open
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Change sidebar status if is open or not
|
||||
* @param {boolean} status is true, if the sidebar is open
|
||||
*/
|
||||
sidebarStatus = (status: boolean) => {
|
||||
this.setState({
|
||||
sidebarStatus: status
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Render DOM component
|
||||
*
|
||||
* @returns DOM
|
||||
*
|
||||
* @memberof Home
|
||||
*/
|
||||
render () {
|
||||
|
||||
return (
|
||||
<div id='home'>
|
||||
<HomeHeader sidebar={this.state.sidebarOpen} sidebarStatus={this.state.sidebarStatus} />
|
||||
<Sidebar overlay={this.sidebarOverlay} open={this.sidebar} status={this.sidebarStatus}>
|
||||
<SidebarContent>
|
||||
<Menu style={{ color: 'rgb(117, 117, 117)', width: '210px' }}>
|
||||
{this.state.sidebarOverlay
|
||||
? <div><MenuItem onClick={this.handleCloseSidebar} primaryText={<span style={{ color: 'rgb(117, 117, 117)' }} className='sidebar__title'>Green</span>} rightIcon={<SvgArrowLeft viewBox='0 3 24 24' style={{ color: '#fff', marginLeft: '15px', width: '32px', height: '32px', cursor: 'pointer' }} />} /><Divider /></div>
|
||||
: ''
|
||||
}
|
||||
|
||||
<NavLink to='/'><MenuItem primaryText='Home' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgHome />} /></NavLink>
|
||||
<NavLink to={`/${this.props.uid}`}><MenuItem primaryText='Profile' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgAccountCircle />} /></NavLink>
|
||||
<NavLink to='/people'><MenuItem primaryText='People' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgPeople />} /></NavLink>
|
||||
<Divider />
|
||||
<NavLink to='/settings'><MenuItem primaryText='Settings' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgSettings />} /></NavLink>
|
||||
<NavLink to='#'><MenuItem primaryText='Send feedback' style={{ color: 'rgb(117, 117, 117)' }} leftIcon={<SvgFeedback />} /></NavLink>
|
||||
</Menu>
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarMain>
|
||||
<Switch>
|
||||
<Route path='/people/:tab?' render={() => {
|
||||
return (
|
||||
this.props.authed
|
||||
? <People />
|
||||
: <Redirect to='/login' />
|
||||
)
|
||||
}} />
|
||||
<Route path='/tag/:tag' render={({match}) => {
|
||||
|
||||
return (
|
||||
this.props.authed
|
||||
? <div className='blog'><StreamComponent displayWriting={false} homeTitle={`#${match.params.tag}`} posts={this.props.mergedPosts} /></div>
|
||||
: <Redirect to='/login' />
|
||||
)
|
||||
}} />
|
||||
<Route path='/:userId/posts/:postId/:tag?' component={PostPage} />
|
||||
<Route path='/:userId' component={Profile} />
|
||||
|
||||
<Route path='/' render={() => {
|
||||
|
||||
return (
|
||||
this.props.authed
|
||||
? <div className='blog'><StreamComponent homeTitle='Home' posts={this.props.mergedPosts} displayWriting={true} /></div>
|
||||
: <Redirect to='/login' />
|
||||
)
|
||||
}} />
|
||||
</Switch>
|
||||
</SidebarMain>
|
||||
</Sidebar>
|
||||
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IHomeComponentProps) => {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: IHomeComponentProps) => {
|
||||
const { uid } = state.authorize
|
||||
let mergedPosts = {}
|
||||
const circles = state.circle ? (state.circle.userCircles[uid] || {}) : {}
|
||||
const followingUsers = CircleAPI.getFollowingUsers(circles)
|
||||
const posts = state.post.userPosts ? state.post.userPosts[state.authorize.uid] : {}
|
||||
Object.keys(followingUsers).forEach((userId) => {
|
||||
let newPosts = state.post.userPosts ? state.post.userPosts[userId] : {}
|
||||
_.merge(mergedPosts,newPosts)
|
||||
})
|
||||
_.merge(mergedPosts,posts)
|
||||
return {
|
||||
authed: state.authorize.authed,
|
||||
mainStyle: state.global.sidebarMainStyle,
|
||||
mergedPosts
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(HomeComponent as any))
|
||||
28
src/components/home/IHomeComponentProps.ts
Normal file
28
src/components/home/IHomeComponentProps.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Post } from 'core/domain/posts'
|
||||
|
||||
export interface IHomeComponentProps {
|
||||
|
||||
/**
|
||||
* Current user is authenticated {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IHomeComponentProps
|
||||
*/
|
||||
authed?: boolean
|
||||
|
||||
/**
|
||||
* User identifier
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IHomeComponentProps
|
||||
*/
|
||||
uid: string
|
||||
|
||||
/**
|
||||
* Merged all users posts to show in stream
|
||||
*
|
||||
* @type {{[postId: string]: Post}}
|
||||
* @memberof IHomeComponentProps
|
||||
*/
|
||||
mergedPosts?: {[postId: string]: Post}
|
||||
}
|
||||
27
src/components/home/IHomeComponentState.ts
Normal file
27
src/components/home/IHomeComponentState.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
|
||||
export interface IHomeComponentState {
|
||||
|
||||
/**
|
||||
* Change sidebar status to {open(status:true)/close(status:false)}
|
||||
*
|
||||
* @type {(status: boolean, state: string)}
|
||||
* @memberof IHomeComponentState
|
||||
*/
|
||||
sidebarOpen: (status: boolean, source: string) => void
|
||||
|
||||
/**
|
||||
* Sidebar status
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IHomeComponentState
|
||||
*/
|
||||
sidebarStatus: boolean
|
||||
|
||||
/**
|
||||
* Sidebar overlay status
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IHomeComponentState
|
||||
*/
|
||||
sidebarOverlay: boolean
|
||||
}
|
||||
2
src/components/home/index.ts
Normal file
2
src/components/home/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import HomeComponent from './HomeComponent'
|
||||
export default HomeComponent
|
||||
278
src/components/homeHeader/HomeHeaderComponent.tsx
Normal file
278
src/components/homeHeader/HomeHeaderComponent.tsx
Normal file
@@ -0,0 +1,278 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { connect } from 'react-redux'
|
||||
import SvgDehaze from 'material-ui/svg-icons/image/dehaze'
|
||||
import { green700, grey400, blue500 } from 'material-ui/styles/colors'
|
||||
import { Toolbar, ToolbarGroup, ToolbarSeparator, ToolbarTitle } from 'material-ui/Toolbar'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import RaisedButton from 'material-ui/RaisedButton'
|
||||
import Popover, { PopoverAnimationVertical } from 'material-ui/Popover'
|
||||
import Menu from 'material-ui/Menu'
|
||||
import MenuItem from 'material-ui/MenuItem'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import NotificationsIcon from 'material-ui/svg-icons/social/notifications'
|
||||
import EventListener, { withOptions } from 'react-event-listener'
|
||||
|
||||
// - Import components
|
||||
import UserAvatarComponent from 'components/userAvatar'
|
||||
import Notify from 'components/notify'
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import { authorizeActions } from 'actions'
|
||||
import { IHomeHeaderComponentProps } from './IHomeHeaderComponentProps'
|
||||
import { IHomeHeaderComponentState } from './IHomeHeaderComponentState'
|
||||
|
||||
// - Create HomeHeader component class
|
||||
export class HomeHeaderComponent extends Component<IHomeHeaderComponentProps,IHomeHeaderComponentState> {
|
||||
|
||||
styles = {
|
||||
toolbarStyle: {
|
||||
backgroundColor: '',
|
||||
transition: 'all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms',
|
||||
boxSizing: 'border-box',
|
||||
fontFamily: 'Roboto, sans-serif',
|
||||
position: 'fixed',
|
||||
zIndex: '1101',
|
||||
width: '100%',
|
||||
top: '0px',
|
||||
boxShadow: '0 1px 8px rgba(0,0,0,.3)'
|
||||
},
|
||||
avatarStyle: {
|
||||
margin: 5,
|
||||
cursor: 'pointer'
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: IHomeHeaderComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Default state
|
||||
this.state = {
|
||||
/**
|
||||
* User avatar popover is open if true
|
||||
*/
|
||||
openAvatarMenu: false,
|
||||
/**
|
||||
* Show header title or not (true/false)
|
||||
*/
|
||||
showTitle: true,
|
||||
/**
|
||||
* If true notification menu will be open
|
||||
*/
|
||||
openNotifyMenu: false
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
this.onToggleSidebar = this.onToggleSidebar.bind(this)
|
||||
this.handleCloseNotify = this.handleCloseNotify.bind(this)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle close notification menu
|
||||
*
|
||||
*
|
||||
* @memberof HomeHeader
|
||||
*/
|
||||
handleCloseNotify = () => {
|
||||
this.setState({
|
||||
openNotifyMenu: false
|
||||
})
|
||||
}
|
||||
|
||||
// On click toggle sidebar
|
||||
onToggleSidebar = () => {
|
||||
if (this.props.sidebarStatus) {
|
||||
this.props.sidebar!(false,'onToggle')
|
||||
|
||||
} else {
|
||||
this.props.sidebar!(true,'onToggle')
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle notification touch
|
||||
*
|
||||
*
|
||||
* @memberof HomeHeader
|
||||
*/
|
||||
handleNotifyTouchTap = (event: any) => {
|
||||
// This prevents ghost click.
|
||||
event.preventDefault()
|
||||
|
||||
this.setState({
|
||||
openNotifyMenu: true,
|
||||
anchorEl: event.currentTarget
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle touch on user avatar for popover
|
||||
*
|
||||
*
|
||||
* @memberof HomeHeader
|
||||
*/
|
||||
handleAvatarTouchTap = (event: any) => {
|
||||
// This prevents ghost click.
|
||||
event.preventDefault()
|
||||
|
||||
this.setState({
|
||||
openAvatarMenu: true,
|
||||
anchorEl: event.currentTarget
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle logout user
|
||||
*
|
||||
*
|
||||
* @memberof HomeHeader
|
||||
*/
|
||||
handleLogout = () => {
|
||||
this.props.logout!()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle close popover
|
||||
*
|
||||
*
|
||||
* @memberof HomeHeader
|
||||
*/
|
||||
handleRequestClose = () => {
|
||||
this.setState({
|
||||
openAvatarMenu: false
|
||||
})
|
||||
}
|
||||
|
||||
handleKeyUp = () => {
|
||||
// TODO: Handle key up on press ESC to close menu
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle resize event for window to manipulate home header status
|
||||
* @param {event} evt is the event is passed by winodw resize event
|
||||
*/
|
||||
handleResize = (event: any) => {
|
||||
|
||||
// Set initial state
|
||||
let width = window.innerWidth
|
||||
|
||||
if (width >= 600 && !this.state.showTitle) {
|
||||
this.setState({
|
||||
showTitle: true
|
||||
})
|
||||
|
||||
} else if (width < 600 && this.state.showTitle) {
|
||||
|
||||
this.setState({
|
||||
showTitle: false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.handleResize(null)
|
||||
}
|
||||
|
||||
// Render app DOM component
|
||||
render () {
|
||||
|
||||
return (
|
||||
|
||||
<Toolbar style={this.styles.toolbarStyle as any} className='g__greenBox'>
|
||||
<EventListener
|
||||
target='window'
|
||||
onResize={this.handleResize}
|
||||
onKeyUp={this.handleKeyUp}
|
||||
/>
|
||||
{/* Left side */}
|
||||
<ToolbarGroup firstChild={true}>
|
||||
|
||||
<IconButton iconStyle={{ color: '#fff' }} onClick={this.onToggleSidebar} >
|
||||
<SvgDehaze style={{ color: '#fff', marginLeft: '15px', cursor: 'pointer' }} />
|
||||
</IconButton>
|
||||
{/* Header title */}
|
||||
<ToolbarTitle style={{ color: '#fff', marginLeft: '15px' }} text='Green' />
|
||||
{this.state.showTitle ? <div className='homeHeader__page'>{this.props.title}</div> : ''}
|
||||
</ToolbarGroup>
|
||||
<ToolbarGroup>
|
||||
|
||||
</ToolbarGroup>
|
||||
|
||||
{/* Notification */}
|
||||
<ToolbarGroup lastChild={true}>
|
||||
<div className='homeHeader__right'>
|
||||
{this.props.notifyCount! > 0 ? (<IconButton tooltip='Notifications' onTouchTap={this.handleNotifyTouchTap}>
|
||||
<div className='homeHeader__notify'>
|
||||
<div className='title'>{this.props.notifyCount}</div>
|
||||
</div>
|
||||
</IconButton>)
|
||||
|
||||
: (<IconButton tooltip='Notifications' onTouchTap={this.handleNotifyTouchTap}>
|
||||
<NotificationsIcon color='rgba(255, 255, 255, 0.87)' />
|
||||
</IconButton>)}
|
||||
<Notify open={this.state.openNotifyMenu} anchorEl={this.state.anchorEl} onRequestClose={this.handleCloseNotify}/>
|
||||
|
||||
{/* User avatar*/}
|
||||
<UserAvatarComponent
|
||||
onTouchTap={this.handleAvatarTouchTap}
|
||||
fullName={this.props.fullName!}
|
||||
fileName={this.props.avatar!}
|
||||
size={32}
|
||||
style={this.styles.avatarStyle}
|
||||
/>
|
||||
<Popover
|
||||
open={this.state.openAvatarMenu}
|
||||
anchorEl={this.state.anchorEl}
|
||||
anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }}
|
||||
targetOrigin={{ horizontal: 'left', vertical: 'top' }}
|
||||
onRequestClose={this.handleRequestClose}
|
||||
>
|
||||
<Menu>
|
||||
<MenuItem style={{ backgroundColor: 'white', color: blue500, fontSize: '14px' }} primaryText='MY ACCOUNT' />
|
||||
<MenuItem primaryText='LOGOUT' style={{ fontSize: '14px' }} onClick={this.handleLogout.bind(this)} />
|
||||
|
||||
</Menu>
|
||||
</Popover>
|
||||
</div>
|
||||
</ToolbarGroup>
|
||||
|
||||
</Toolbar>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// - Map dispatch to props
|
||||
const mapDispatchToProps = (dispatch: Function, ownProps: IHomeHeaderComponentProps) => {
|
||||
return {
|
||||
logout: () => dispatch(authorizeActions.dbLogout())
|
||||
}
|
||||
}
|
||||
|
||||
// - Map state to props
|
||||
const mapStateToProps = (state: any, ownProps: IHomeHeaderComponentProps) => {
|
||||
|
||||
let notifyCount = state.notify.userNotifies
|
||||
? Object
|
||||
.keys(state.notify.userNotifies)
|
||||
.filter((key) => !state.notify.userNotifies[key].isSeen).length
|
||||
: 0
|
||||
return {
|
||||
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : '',
|
||||
fullName: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].fullName : '',
|
||||
title: state.global.headerTitle,
|
||||
notifyCount
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(HomeHeaderComponent as any)
|
||||
63
src/components/homeHeader/IHomeHeaderComponentProps.ts
Normal file
63
src/components/homeHeader/IHomeHeaderComponentProps.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import StringAPI from 'api/StringAPI'
|
||||
export interface IHomeHeaderComponentProps {
|
||||
|
||||
/**
|
||||
* Sidebar is open {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
sidebarStatus?: boolean
|
||||
|
||||
/**
|
||||
* Logout user
|
||||
*
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
logout?: () => void
|
||||
|
||||
/**
|
||||
* Handle on resize window event
|
||||
*
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
handleResize?: (event: any) => void
|
||||
|
||||
/**
|
||||
* Number of notifications
|
||||
*
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
notifyCount?: number
|
||||
|
||||
/**
|
||||
* User full name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
fullName?: string
|
||||
|
||||
/**
|
||||
* User's avatar URL address
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
avatar?: string
|
||||
|
||||
/**
|
||||
* Top bar title
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
title?: string
|
||||
|
||||
/**
|
||||
* Toggle sidebar
|
||||
*
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
sidebar?: (status: boolean, source: string) => void
|
||||
}
|
||||
35
src/components/homeHeader/IHomeHeaderComponentState.ts
Normal file
35
src/components/homeHeader/IHomeHeaderComponentState.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
|
||||
export interface IHomeHeaderComponentState {
|
||||
|
||||
/**
|
||||
* Popover menu on avatar is open {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IHomeHeaderComponentState
|
||||
*/
|
||||
openAvatarMenu: boolean
|
||||
|
||||
/**
|
||||
* Show top bar title {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IHomeHeaderComponentState
|
||||
*/
|
||||
showTitle: boolean
|
||||
|
||||
/**
|
||||
* Notification menu is open {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IHomeHeaderComponentState
|
||||
*/
|
||||
openNotifyMenu: boolean
|
||||
|
||||
/**
|
||||
* This is the DOM element that will be used to set the position of the popover.
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof IHomeHeaderComponentState
|
||||
*/
|
||||
anchorEl?: HTMLElement
|
||||
}
|
||||
2
src/components/homeHeader/index.ts
Normal file
2
src/components/homeHeader/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import HomeHeaderComponent from './HomeHeaderComponent'
|
||||
export default HomeHeaderComponent
|
||||
48
src/components/imageGallery/IImageGalleryComponentProps.ts
Normal file
48
src/components/imageGallery/IImageGalleryComponentProps.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Image } from 'core/domain/imageGallery'
|
||||
|
||||
export interface IImageGalleryComponentProps {
|
||||
|
||||
/**
|
||||
* Select image from image gallery
|
||||
*
|
||||
* @type {(URL: string,fullPath: string)}
|
||||
* @memberof IImageGalleryComponentProps
|
||||
*/
|
||||
set?: (URL: string,fullPath: string) => void
|
||||
|
||||
/**
|
||||
* Delete an image
|
||||
*
|
||||
* @memberof IImageGalleryComponentProps
|
||||
*/
|
||||
deleteImage?: (imageId: string) => void
|
||||
|
||||
/**
|
||||
* Save image in image gallery
|
||||
*
|
||||
* @memberof IImageGalleryComponentProps
|
||||
*/
|
||||
saveImageGallery?: (URL: string,fullPath: string) => void
|
||||
|
||||
/**
|
||||
* Change progress state
|
||||
*
|
||||
* @memberof IImageGalleryComponentProps
|
||||
*/
|
||||
progressChange?: (percentage: number, status: boolean) => void
|
||||
|
||||
/**
|
||||
* Close image gallery
|
||||
*
|
||||
* @memberof IImageGalleryComponentProps
|
||||
*/
|
||||
close?: () => void
|
||||
|
||||
/**
|
||||
* List of image in image gallery
|
||||
*
|
||||
* @type {Image[]}
|
||||
* @memberof IImageGalleryComponentProps
|
||||
*/
|
||||
images?: Image[]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface IImageGalleryComponentState {
|
||||
|
||||
}
|
||||
256
src/components/imageGallery/ImageGalleryComponent.tsx
Normal file
256
src/components/imageGallery/ImageGalleryComponent.tsx
Normal file
@@ -0,0 +1,256 @@
|
||||
// - Impoer react components
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { GridList, GridTile } from 'material-ui/GridList'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import Subheader from 'material-ui/Subheader'
|
||||
import StarBorder from 'material-ui/svg-icons/toggle/star-border'
|
||||
import FloatingActionButton from 'material-ui/FloatingActionButton'
|
||||
import SvgUpload from 'material-ui/svg-icons/file/cloud-upload'
|
||||
import SvgAddImage from 'material-ui/svg-icons/image/add-a-photo'
|
||||
import SvgDelete from 'material-ui/svg-icons/action/delete'
|
||||
import { grey200, grey600 } from 'material-ui/styles/colors'
|
||||
import FlatButton from 'material-ui/FlatButton'
|
||||
import uuid from 'uuid'
|
||||
|
||||
// - Import actions
|
||||
import * as imageGalleryActions from 'actions/imageGalleryActions'
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
|
||||
// - Import app components
|
||||
import Img from 'components/img'
|
||||
|
||||
// - Import API
|
||||
import FileAPI from 'api/FileAPI'
|
||||
import { IImageGalleryComponentProps } from './IImageGalleryComponentProps'
|
||||
import { IImageGalleryComponentState } from './IImageGalleryComponentState'
|
||||
import { Image } from 'core/domain/imageGallery'
|
||||
|
||||
/**
|
||||
* Create ImageGallery component class
|
||||
*/
|
||||
export class ImageGalleryComponent extends Component<IImageGalleryComponentProps, IImageGalleryComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
|
||||
/**
|
||||
* Callback function to ser image url on parent component
|
||||
*/
|
||||
open: PropTypes.func
|
||||
}
|
||||
|
||||
styles = {
|
||||
root: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-around'
|
||||
},
|
||||
gridList: {
|
||||
width: 500,
|
||||
height: 450,
|
||||
overflowY: 'auto'
|
||||
},
|
||||
uploadButton: {
|
||||
verticalAlign: 'middle'
|
||||
},
|
||||
uploadInput: {
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
width: '100%',
|
||||
opacity: 0
|
||||
},
|
||||
deleteImage: {
|
||||
marginLeft: '5px',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
addImage: {
|
||||
marginRight: '5px',
|
||||
cursor: 'pointer'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: IImageGalleryComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Binding function to `this`
|
||||
this.close = this.close.bind(this)
|
||||
this.onFileChange = this.onFileChange.bind(this)
|
||||
this.handleSetImage = this.handleSetImage.bind(this)
|
||||
this.handleDeleteImage = this.handleDeleteImage.bind(this)
|
||||
this.imageList = this.imageList.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle set image
|
||||
* @param {event} evt passed by on click event on add image
|
||||
* @param {string} name is the name of the image
|
||||
*/
|
||||
handleSetImage = (event: any, URL: string,fullPath: string) => {
|
||||
this.props.set!(URL,fullPath)
|
||||
this.close()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle delete image
|
||||
* @param {event} evt passed by on click event on delete image
|
||||
* @param {integer} id is the image identifier which selected to delete
|
||||
*/
|
||||
handleDeleteImage = (event: any, id: string) => {
|
||||
this.props.deleteImage!(id)
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
window.addEventListener('onSendResizedImage', this.handleSendResizedImage)
|
||||
}
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('onSendResizedImage', this.handleSendResizedImage)
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle send image resize event that pass the resized image
|
||||
*
|
||||
*
|
||||
* @memberof ImageGallery
|
||||
*/
|
||||
handleSendResizedImage = (event: any) => {
|
||||
|
||||
const { resizedImage, fileName } = event.detail
|
||||
const {saveImageGallery, progressChange} = this.props
|
||||
|
||||
FileAPI.uploadImage(resizedImage, fileName, (percent: number, status: boolean) => {
|
||||
progressChange!(percent,status)
|
||||
}).then((result) => {
|
||||
|
||||
/* Add image to image gallery */
|
||||
saveImageGallery!(result.downloadURL,result.metadata.fullPath)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle on change file upload
|
||||
*/
|
||||
onFileChange = (event: any) => {
|
||||
|
||||
const extension = FileAPI.getExtension(event.target.files[0].name)
|
||||
let fileName = (`${uuid()}.${extension}`)
|
||||
let image = FileAPI.constraintImage(event.target.files[0], fileName)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide image gallery
|
||||
*/
|
||||
close = () => {
|
||||
this.props.close!()
|
||||
}
|
||||
|
||||
imageList = () => {
|
||||
|
||||
return this.props.images!.map((image: Image, index) => {
|
||||
|
||||
return (<GridTile
|
||||
key={image.id!}
|
||||
title={<SvgDelete hoverColor={grey200} color='white' style={this.styles.deleteImage as any} onClick={evt => this.handleDeleteImage(evt, image.id!)} />}
|
||||
subtitle={<span></span>}
|
||||
actionIcon={<SvgAddImage hoverColor={grey200} color='white' style={this.styles.addImage as any} onClick={evt => this.handleSetImage(evt, image.URL,image.fullPath)} />}
|
||||
>
|
||||
<div>
|
||||
<div style={{ overflowY: 'hidden', overflowX: 'auto' }}>
|
||||
<ul style={{ whiteSpace: 'nowrap', padding: '0 6px', margin: '8px 0 0 0', verticalAlign: 'bottom', flexShrink: 0, listStyleType: 'none' }}>
|
||||
<div style={{ display: 'block' }}>
|
||||
<div style={{ display: 'block', marginRight: '8px', transition: 'transform .25s' }}>
|
||||
<li style={{ width: '100%', margin: 0, verticalAlign: 'bottom', position: 'static', display: 'inline-block' }}>
|
||||
<Img fileName={image.URL} style={{ width: '100%', height: 'auto' }} />
|
||||
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</GridTile>)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* When the post text changed
|
||||
* @param {event} evt is an event passed by change post text callback funciton
|
||||
* @param {string} data is the post content which user writes
|
||||
*/
|
||||
render () {
|
||||
|
||||
/**
|
||||
* Component styles
|
||||
* @type {Object}
|
||||
*/
|
||||
|
||||
return (
|
||||
<div style={this.styles.root as any}>
|
||||
<GridList
|
||||
cellHeight={180}
|
||||
style={this.styles.gridList as any}
|
||||
>
|
||||
<GridTile >
|
||||
|
||||
<div style={{ display: 'flex', backgroundColor: 'rgba(222, 222, 222, 0.52)', flexDirection: 'column', justifyContent: 'center', alignItems: 'center', height: '100%' }}>
|
||||
|
||||
<FlatButton
|
||||
label='Upload Photo'
|
||||
labelStyle={{ fontWeight: 100 }}
|
||||
labelPosition='before'
|
||||
style={this.styles.uploadButton}
|
||||
containerElement='label'
|
||||
>
|
||||
<input type='file' onChange={this.onFileChange} accept='image/*' style={this.styles.uploadInput as any} />
|
||||
</FlatButton>
|
||||
</div>
|
||||
</GridTile>
|
||||
{this.imageList()}
|
||||
</GridList>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IImageGalleryComponentProps) => {
|
||||
return {
|
||||
saveImageGallery: (imageURL: string,imageFullPath: string) => dispatch(imageGalleryActions.dbSaveImage(imageURL,imageFullPath)),
|
||||
deleteImage: (id: string) => dispatch(imageGalleryActions.dbDeleteImage(id)),
|
||||
progressChange : (percent: number,status: boolean) => dispatch(globalActions.progressChange(percent, status))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any) => {
|
||||
return {
|
||||
images: state.imageGallery.images,
|
||||
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : ''
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ImageGalleryComponent as any)
|
||||
2
src/components/imageGallery/index.ts
Normal file
2
src/components/imageGallery/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import ImageGalleryComponent from './ImageGalleryComponent'
|
||||
export default ImageGalleryComponent
|
||||
19
src/components/img/IImgComponentProps.ts
Normal file
19
src/components/img/IImgComponentProps.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export interface IImgComponentProps {
|
||||
|
||||
/**
|
||||
* Image file name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IImgComponentProps
|
||||
*/
|
||||
fileName: string
|
||||
|
||||
/**
|
||||
* Image style sheet
|
||||
*
|
||||
* @type {{}}
|
||||
* @memberof IImgComponentProps
|
||||
*/
|
||||
style?: {}
|
||||
|
||||
}
|
||||
11
src/components/img/IImgComponentState.ts
Normal file
11
src/components/img/IImgComponentState.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
export interface IImgComponentState {
|
||||
|
||||
/**
|
||||
* Image is loaded {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IImgComponentProps
|
||||
*/
|
||||
isImageLoaded?: boolean
|
||||
}
|
||||
120
src/components/img/ImgComponent.tsx
Normal file
120
src/components/img/ImgComponent.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import SvgImage from 'material-ui/svg-icons/image/image'
|
||||
|
||||
// - Import app components
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as imageGalleryActions from 'actions/imageGalleryActions'
|
||||
import { IImgComponentProps } from './IImgComponentProps'
|
||||
import { IImgComponentState } from './IImgComponentState'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class ImgComponent extends Component<IImgComponentProps,IImgComponentState> {
|
||||
|
||||
styles = {
|
||||
loding: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: '100px',
|
||||
position: 'relative',
|
||||
color: '#cacecd',
|
||||
fontWeight: 100
|
||||
},
|
||||
loadingContent: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
},
|
||||
loadingImage: {
|
||||
fill: 'aliceblue',
|
||||
width: '50px',
|
||||
height: '50px'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: IImgComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Defaul state
|
||||
this.state = {
|
||||
isImageLoaded: false
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
this.handleLoadImage = this.handleLoadImage.bind(this)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be called on loading image
|
||||
*
|
||||
* @memberof Img
|
||||
*/
|
||||
handleLoadImage = () => {
|
||||
this.setState({
|
||||
isImageLoaded: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
|
||||
let { fileName, style } = this.props
|
||||
let { isImageLoaded } = this.state
|
||||
return (
|
||||
<div>
|
||||
<img onLoad={this.handleLoadImage} src={fileName || ''} style={isImageLoaded ? style : { display: 'none' }} />
|
||||
<div style={Object.assign({},{ backgroundColor: 'white' }, isImageLoaded ? { display: 'none' } : this.styles.loding) }>
|
||||
<div style={this.styles.loadingContent as any}>
|
||||
<SvgImage style={this.styles.loadingImage} />
|
||||
<div>Image has not loaded</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IImgComponentProps) => {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: IImgComponentProps) => {
|
||||
return {
|
||||
avatarURL: state.imageGallery.imageURLList,
|
||||
imageRequests: state.imageGallery.imageRequests
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ImgComponent as any)
|
||||
2
src/components/img/index.ts
Normal file
2
src/components/img/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import ImgComponent from './ImgComponent'
|
||||
export default ImgComponent
|
||||
42
src/components/imgCover/IImgCoverComponentProps.ts
Normal file
42
src/components/imgCover/IImgCoverComponentProps.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
export interface IImgCoverComponentProps {
|
||||
|
||||
/**
|
||||
* Image file name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IImgCoverComponentProps
|
||||
*/
|
||||
fileName: string
|
||||
|
||||
/**
|
||||
* Image style sheet
|
||||
*
|
||||
* @type {{}}
|
||||
* @memberof IImgCoverComponentProps
|
||||
*/
|
||||
style?: {}
|
||||
|
||||
/**
|
||||
* Image with
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IImgCoverComponentProps
|
||||
*/
|
||||
width?: string
|
||||
|
||||
/**
|
||||
* Image height
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IImgCoverComponentProps
|
||||
*/
|
||||
height?: string
|
||||
|
||||
/**
|
||||
* Image border radius
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IImgCoverComponentProps
|
||||
*/
|
||||
borderRadius?: string
|
||||
}
|
||||
12
src/components/imgCover/IImgCoverComponentState.ts
Normal file
12
src/components/imgCover/IImgCoverComponentState.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
export interface IImgCoverComponentState {
|
||||
|
||||
/**
|
||||
* Image is loaded {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IImgCoverComponentProps
|
||||
*/
|
||||
isImageLoaded: boolean
|
||||
|
||||
}
|
||||
162
src/components/imgCover/ImgCoverComponent.tsx
Normal file
162
src/components/imgCover/ImgCoverComponent.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import SvgImage from 'material-ui/svg-icons/image/image'
|
||||
|
||||
// - Import app components
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as imageGalleryActions from 'actions/imageGalleryActions'
|
||||
import { IImgCoverComponentProps } from './IImgCoverComponentProps'
|
||||
import { IImgCoverComponentState } from './IImgCoverComponentState'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class ImgCoverComponent extends Component<IImgCoverComponentProps,IImgCoverComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
|
||||
/**
|
||||
* Use for getting url address from server
|
||||
*/
|
||||
fileName: PropTypes.string,
|
||||
/**
|
||||
* Image width
|
||||
*/
|
||||
width: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
]).isRequired,
|
||||
/**
|
||||
* Image height
|
||||
*/
|
||||
height: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
]).isRequired,
|
||||
/**
|
||||
* Image border radius
|
||||
*/
|
||||
borderRadius: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
])
|
||||
}
|
||||
|
||||
styles = {
|
||||
cover: {
|
||||
backgroundSize: 'cover',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundPosition: 'center'
|
||||
},
|
||||
loding: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
height: '100px',
|
||||
position: 'relative',
|
||||
color: '#cacecd',
|
||||
fontWeight: 100
|
||||
},
|
||||
loadingContent: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
},
|
||||
loadingImage: {
|
||||
fill: 'aliceblue',
|
||||
width: '50px',
|
||||
height: '50px'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: IImgCoverComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Defaul state
|
||||
this.state = {
|
||||
isImageLoaded: false
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
this.handleLoadImage = this.handleLoadImage.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Will be called on loading image
|
||||
*
|
||||
* @memberof ImgCoverComponent
|
||||
*/
|
||||
handleLoadImage = () => {
|
||||
this.setState({
|
||||
isImageLoaded: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
|
||||
let { fileName, style } = this.props
|
||||
let { isImageLoaded } = this.state
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={Object.assign({},this.styles.cover,{
|
||||
backgroundImage: 'url(' + (fileName || '') + ')',
|
||||
width: this.props.width,
|
||||
height: this.props.height,
|
||||
borderRadius: this.props.borderRadius
|
||||
},style)}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
<div style={Object.assign({},{ backgroundColor: 'blue' },isImageLoaded ? { display: 'none' } : this.styles.loding)}>
|
||||
<div style={this.styles.loadingContent as any}>
|
||||
<SvgImage style={this.styles.loadingImage} />
|
||||
<div>Image has not loaded</div>
|
||||
</div>
|
||||
</div>
|
||||
<img onLoad={this.handleLoadImage} src={fileName || ''} style={{ display: 'none'}} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IImgCoverComponentProps) => {
|
||||
return {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: IImgCoverComponentProps) => {
|
||||
return {
|
||||
avatarURL: state.imageGallery.imageURLList,
|
||||
imageRequests: state.imageGallery.imageRequests
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ImgCoverComponent as any)
|
||||
2
src/components/imgCover/index.ts
Normal file
2
src/components/imgCover/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import ImgCoverComponent from './ImgCoverComponent'
|
||||
export default ImgCoverComponent
|
||||
16
src/components/login/ILoginComponentProps.ts
Normal file
16
src/components/login/ILoginComponentProps.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface ILoginComponentProps {
|
||||
|
||||
/**
|
||||
* Login a user
|
||||
*
|
||||
* @memberof ILoginComponentProps
|
||||
*/
|
||||
login?: (email: string , password: string) => any
|
||||
|
||||
/**
|
||||
* Redirect to signup page
|
||||
*
|
||||
* @memberof ILoginComponentProps
|
||||
*/
|
||||
signupPage?: () => any
|
||||
}
|
||||
43
src/components/login/ILoginComponentState.ts
Normal file
43
src/components/login/ILoginComponentState.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
|
||||
export interface ILoginComponentState {
|
||||
|
||||
/**
|
||||
* Email input value
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ILoginComponentState
|
||||
*/
|
||||
emailInput: string
|
||||
|
||||
/**
|
||||
* Email input error text
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ILoginComponentState
|
||||
*/
|
||||
emailInputError: string
|
||||
|
||||
/**
|
||||
* Password input value
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ILoginComponentState
|
||||
*/
|
||||
passwordInput: string
|
||||
|
||||
/**
|
||||
* Password input error text
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ILoginComponentState
|
||||
*/
|
||||
passwordInputError: string
|
||||
|
||||
/**
|
||||
* Confirm input error text
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ILoginComponentState
|
||||
*/
|
||||
confirmInputError: string
|
||||
}
|
||||
209
src/components/login/LoginComponent.tsx
Normal file
209
src/components/login/LoginComponent.tsx
Normal file
@@ -0,0 +1,209 @@
|
||||
// - Import external components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { NavLink, withRouter } from 'react-router-dom'
|
||||
import { push } from 'react-router-redux'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import TextField from 'material-ui/TextField'
|
||||
import RaisedButton from 'material-ui/RaisedButton'
|
||||
import FlatButton from 'material-ui/FlatButton'
|
||||
|
||||
// - Import actions
|
||||
import * as authorizeActions from 'actions/authorizeActions'
|
||||
import { ILoginComponentProps } from './ILoginComponentProps'
|
||||
import { ILoginComponentState } from './ILoginComponentState'
|
||||
|
||||
// - Create Login component class
|
||||
export class LoginComponent extends Component<ILoginComponentProps,ILoginComponentState> {
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: ILoginComponentProps) {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
emailInput: '',
|
||||
emailInputError: '',
|
||||
passwordInput: '',
|
||||
passwordInputError: '',
|
||||
confirmInputError: ''
|
||||
}
|
||||
// Binding function to `this`
|
||||
this.handleForm = this.handleForm.bind(this)
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle data on input change
|
||||
* @param {event} evt is an event of inputs of element on change
|
||||
*/
|
||||
handleInputChange = (event: any) => {
|
||||
const target = event.target
|
||||
const value = target.type === 'checkbox' ? target.checked : target.value
|
||||
const name = target.name
|
||||
this.setState({
|
||||
[name]: value
|
||||
})
|
||||
|
||||
switch (name) {
|
||||
case 'emailInput':
|
||||
this.setState({
|
||||
emailInputError: ''
|
||||
})
|
||||
break
|
||||
case 'passwordInput':
|
||||
this.setState({
|
||||
confirmInputError: '',
|
||||
passwordInputError: ''
|
||||
})
|
||||
|
||||
break
|
||||
default:
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle register form
|
||||
*/
|
||||
handleForm = () => {
|
||||
|
||||
let error = false
|
||||
if (this.state.emailInput === '') {
|
||||
this.setState({
|
||||
emailInputError: 'This field is required'
|
||||
})
|
||||
error = true
|
||||
|
||||
}
|
||||
if (this.state.passwordInput === '') {
|
||||
this.setState({
|
||||
passwordInputError: 'This field is required'
|
||||
})
|
||||
error = true
|
||||
|
||||
}
|
||||
|
||||
if (!error) {
|
||||
this.props.login!(
|
||||
this.state.emailInput,
|
||||
this.state.passwordInput
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
|
||||
const paperStyle = {
|
||||
minHeight: 370,
|
||||
width: 450,
|
||||
textAlign: 'center',
|
||||
display: 'block',
|
||||
margin: 'auto'
|
||||
}
|
||||
return (
|
||||
<form>
|
||||
|
||||
<h1 style={{
|
||||
textAlign: 'center',
|
||||
padding: '20px',
|
||||
fontSize: '30px',
|
||||
fontWeight: 500,
|
||||
lineHeight: '32px',
|
||||
margin: 'auto',
|
||||
color: 'rgba(138, 148, 138, 0.2)'
|
||||
}}>Green</h1>
|
||||
|
||||
<div className='animate-bottom'>
|
||||
<Paper style={paperStyle} zDepth={1} rounded={false} >
|
||||
<div style={{ padding: '48px 40px 36px' }}>
|
||||
<div style={{
|
||||
paddingLeft: '40px',
|
||||
paddingRight: '40px'
|
||||
}}>
|
||||
|
||||
<h2 style={{
|
||||
textAlign: 'left',
|
||||
paddingTop: '16px',
|
||||
fontSize: '24px',
|
||||
fontWeight: 400,
|
||||
lineHeight: '32px',
|
||||
margin: 0
|
||||
}}>Sign in</h2>
|
||||
</div>
|
||||
|
||||
<TextField
|
||||
onChange={this.handleInputChange}
|
||||
errorText={this.state.emailInputError}
|
||||
name='emailInput'
|
||||
floatingLabelStyle={{ fontSize: '15px' }}
|
||||
floatingLabelText='Email'
|
||||
type='email'
|
||||
tabIndex={1}
|
||||
/><br />
|
||||
<TextField
|
||||
onChange={this.handleInputChange}
|
||||
errorText={this.state.passwordInputError}
|
||||
name='passwordInput'
|
||||
floatingLabelStyle={{ fontSize: '15px' }}
|
||||
floatingLabelText='Password'
|
||||
type='password'
|
||||
tabIndex={2}
|
||||
/><br />
|
||||
<br />
|
||||
<br />
|
||||
<div className='login__button-box'>
|
||||
<div>
|
||||
<FlatButton label='Create an account' onClick={this.props.signupPage} tabIndex={4} />
|
||||
</div>
|
||||
<div >
|
||||
<RaisedButton label='Login' primary={true} onClick={this.handleForm} tabIndex={3} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</Paper>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: ILoginComponentProps) => {
|
||||
return {
|
||||
login: (email: string, password: string) => {
|
||||
dispatch(authorizeActions.dbLogin(email, password))
|
||||
},
|
||||
signupPage: () => {
|
||||
dispatch(push('/signup'))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: ILoginComponentProps) => {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(LoginComponent as any))
|
||||
2
src/components/login/index.ts
Normal file
2
src/components/login/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import LoginComponent from './LoginComponent'
|
||||
export default LoginComponent
|
||||
100
src/components/master/IMasterComponentProps.ts
Normal file
100
src/components/master/IMasterComponentProps.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
export interface IMasterComponentProps {
|
||||
/**
|
||||
* Close gloal message
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof IMasterProps
|
||||
*/
|
||||
closeMessage: Function,
|
||||
/**
|
||||
* Show progress bar information
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof IMasterProps
|
||||
*/
|
||||
progress: any,
|
||||
/**
|
||||
* Login a user
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof IMasterProps
|
||||
*/
|
||||
login: Function,
|
||||
/**
|
||||
* Global state
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof IMasterProps
|
||||
*/
|
||||
global: any,
|
||||
/**
|
||||
* Set flag {false} which user data has not loaded
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof IMasterProps
|
||||
*/
|
||||
defaultDataDisable: Function,
|
||||
/**
|
||||
* Logout current user
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof IMasterProps
|
||||
*/
|
||||
logout: Function,
|
||||
/**
|
||||
* Clear user date from store
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof IMasterProps
|
||||
*/
|
||||
clearData: Function,
|
||||
/**
|
||||
* Prepare default data for a guest user
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof IMasterProps
|
||||
*/
|
||||
loadDataGuest: Function,
|
||||
/**
|
||||
* Set flag {true} which all user data has loaded
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof IMasterProps
|
||||
*/
|
||||
defaultDataEnable: Function,
|
||||
/**
|
||||
* Load user data into store
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof IMasterProps
|
||||
*/
|
||||
loadData: Function,
|
||||
/**
|
||||
* If all data from all entities are loaded {true} if not {false}
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @memberof IMasterProps
|
||||
*/
|
||||
loaded: Boolean,
|
||||
/**
|
||||
* If current user is guest {true} if no
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @memberof IMasterProps
|
||||
*/
|
||||
guest: Boolean,
|
||||
/**
|
||||
* If current user is authed {true} if not {false}
|
||||
*
|
||||
* @type {Boolean}
|
||||
* @memberof IMasterProps
|
||||
*/
|
||||
authed: Boolean,
|
||||
/**
|
||||
* Authed user identifier
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IMasterProps
|
||||
*/
|
||||
uid: string
|
||||
}
|
||||
24
src/components/master/IMasterComponentState.ts
Normal file
24
src/components/master/IMasterComponentState.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
|
||||
export interface IMasterComponentState {
|
||||
/**
|
||||
* Loding will be appeared if it's true
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IMasterState
|
||||
*/
|
||||
loading: boolean,
|
||||
/**
|
||||
* It's true if user is authorized
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IMasterState
|
||||
*/
|
||||
authed: boolean
|
||||
/**
|
||||
* It's true if all default data loaded from database
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IMasterState
|
||||
*/
|
||||
dataLoaded: boolean
|
||||
}
|
||||
218
src/components/master/MasterComponent.tsx
Normal file
218
src/components/master/MasterComponent.tsx
Normal file
@@ -0,0 +1,218 @@
|
||||
/// <reference types="@types/material-ui" />
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Route, Switch, NavLink, withRouter, Redirect } from 'react-router-dom'
|
||||
import { firebaseAuth, firebaseRef } from 'data/firebaseClient'
|
||||
import { push } from 'react-router-redux'
|
||||
import Snackbar from 'material-ui/Snackbar'
|
||||
import LinearProgress from 'material-ui/LinearProgress'
|
||||
|
||||
// - Import components
|
||||
import Home from 'components/home'
|
||||
import Signup from 'components/signup'
|
||||
import Login from 'components/login'
|
||||
import Setting from 'components/setting'
|
||||
import MasterLoading from 'components/masterLoading'
|
||||
import { IMasterComponentProps } from './IMasterComponentProps'
|
||||
import { IMasterComponentState } from './IMasterComponentState'
|
||||
|
||||
// - Import actions
|
||||
import {
|
||||
authorizeActions,
|
||||
imageGalleryActions,
|
||||
postActions,
|
||||
commentActions,
|
||||
voteActions,
|
||||
userActions,
|
||||
globalActions,
|
||||
circleActions,
|
||||
notifyActions
|
||||
} from 'actions'
|
||||
|
||||
/* ------------------------------------ */
|
||||
|
||||
// - Create Master component class
|
||||
export class MasterComponent extends Component<IMasterComponentProps, IMasterComponentState> {
|
||||
|
||||
static isPrivate = true
|
||||
// Constructor
|
||||
constructor (props: IMasterComponentProps) {
|
||||
super(props)
|
||||
this.state = {
|
||||
loading: true,
|
||||
authed: false,
|
||||
dataLoaded: false
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
this.handleLoading = this.handleLoading.bind(this)
|
||||
this.handleMessage = this.handleMessage.bind(this)
|
||||
|
||||
}
|
||||
|
||||
// Handle click on message
|
||||
handleMessage = (evt: any) => {
|
||||
this.props.closeMessage()
|
||||
}
|
||||
|
||||
// Handle loading
|
||||
handleLoading = (status: boolean) => {
|
||||
this.setState({
|
||||
loading: status,
|
||||
authed: false
|
||||
})
|
||||
}
|
||||
|
||||
componentDidCatch (error: any, info: any) {
|
||||
console.log('===========Catched by React componentDidCatch==============')
|
||||
console.log(error, info)
|
||||
alert({error, info})
|
||||
console.log('====================================')
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
|
||||
firebaseAuth().onAuthStateChanged((user: any) => {
|
||||
|
||||
if (user) {
|
||||
this.props.login(user)
|
||||
this.setState({
|
||||
loading: false
|
||||
})
|
||||
if (!this.props.global.defaultLoadDataStatus) {
|
||||
this.props.clearData()
|
||||
this.props.loadData()
|
||||
this.props.defaultDataEnable()
|
||||
}
|
||||
} else {
|
||||
this.props.logout()
|
||||
this.setState({
|
||||
loading: false
|
||||
})
|
||||
if (this.props.global.defaultLoadDataStatus) {
|
||||
this.props.defaultDataDisable()
|
||||
this.props.clearData()
|
||||
}
|
||||
this.props.loadDataGuest()
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Render app DOM component
|
||||
*
|
||||
* @returns
|
||||
*
|
||||
* @memberof Master
|
||||
*/
|
||||
public render (): React.ReactElement<{}> {
|
||||
|
||||
const { progress, global } = this.props
|
||||
|
||||
return (
|
||||
<div id='master'>
|
||||
|
||||
<div className='master__progress' style={{ display: (progress.visible ? 'block' : 'none') }}>
|
||||
<LinearProgress mode='determinate' value={progress.percent} />
|
||||
</div>
|
||||
<div className='master__loading animate-fading2' style={{ display: (global.showTopLoading ? 'flex' : 'none') }}>
|
||||
<div className='title'>Loading ... </div>
|
||||
</div>
|
||||
<MasterLoading activeLoading={this.state.loading || !(this.props.loaded || this.props.guest)} handleLoading={this.handleLoading} />
|
||||
|
||||
{(!this.state.loading && (this.props.loaded || this.props.guest))
|
||||
? (<Switch>
|
||||
<Route path='/signup' component={Signup} />
|
||||
<Route path='/settings' component={Setting} />
|
||||
<Route path='/login' render={() => {
|
||||
console.log('this.props.authed: ', this.props.authed, 'this.props: ', this.props)
|
||||
return (
|
||||
this.props.authed
|
||||
? <Redirect to='/' />
|
||||
: <Login />
|
||||
)
|
||||
}
|
||||
} />
|
||||
<Route render={() => <Home uid={this.props.uid} />} />
|
||||
|
||||
</Switch>) : ''
|
||||
}
|
||||
<Snackbar
|
||||
open={this.props.global.messageOpen}
|
||||
message={this.props.global.message}
|
||||
autoHideDuration={4000}
|
||||
style={{ left: '1%', transform: 'none' }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// - Map dispatch to props
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IMasterComponentProps) => {
|
||||
|
||||
return {
|
||||
loadData: () => {
|
||||
dispatch(commentActions.dbGetComments())
|
||||
dispatch(imageGalleryActions.dbGetImageGallery())
|
||||
dispatch(postActions.dbGetPosts())
|
||||
dispatch(userActions.dbGetUserInfo())
|
||||
dispatch(voteActions.dbGetVotes())
|
||||
dispatch(notifyActions.dbGetNotifications())
|
||||
dispatch(circleActions.dbGetCircles())
|
||||
|
||||
},
|
||||
clearData: () => {
|
||||
dispatch(imageGalleryActions.clearAllData())
|
||||
dispatch(postActions.clearAllData())
|
||||
dispatch(userActions.clearAllData())
|
||||
dispatch(commentActions.clearAllData())
|
||||
dispatch(voteActions.clearAllvotes())
|
||||
dispatch(notifyActions.clearAllNotifications())
|
||||
dispatch(circleActions.clearAllCircles())
|
||||
dispatch(globalActions.clearTemp())
|
||||
|
||||
},
|
||||
login: (user: any) => {
|
||||
dispatch(authorizeActions.login(user.uid))
|
||||
},
|
||||
logout: () => {
|
||||
dispatch(authorizeActions.logout())
|
||||
},
|
||||
defaultDataDisable: () => {
|
||||
dispatch(globalActions.defaultDataDisable())
|
||||
},
|
||||
defaultDataEnable: () => {
|
||||
dispatch(globalActions.defaultDataEnable())
|
||||
},
|
||||
closeMessage: () => {
|
||||
dispatch(globalActions.hideMessage())
|
||||
},
|
||||
loadDataGuest: () => {
|
||||
dispatch(globalActions.loadDataGuest())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state
|
||||
*/
|
||||
const mapStateToProps = (state: any) => {
|
||||
const { authorize, global, user, post, comment, imageGallery, vote, notify, circle } = state
|
||||
return {
|
||||
guest: authorize.guest,
|
||||
uid: authorize.uid,
|
||||
authed: authorize.authed,
|
||||
progress: global.progress,
|
||||
global: global,
|
||||
loaded: user.loaded && post.loaded && comment.loaded && imageGallery.loaded && vote.loaded && notify.loaded && circle.loaded
|
||||
}
|
||||
|
||||
}
|
||||
// - Connect commponent to redux store
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MasterComponent as any))
|
||||
2
src/components/master/index.ts
Normal file
2
src/components/master/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import MasterComponent from './MasterComponent'
|
||||
export default MasterComponent
|
||||
12
src/components/masterLoading/IMasterLoadingComponentProps.ts
Normal file
12
src/components/masterLoading/IMasterLoadingComponentProps.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface IMasterLoadingComponentProps {
|
||||
|
||||
/**
|
||||
* Loading is active {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IMasterLoadingComponentProps
|
||||
*/
|
||||
activeLoading: boolean
|
||||
|
||||
handleLoading: (status: boolean) => void
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface IMasterLoadingComponentState {
|
||||
|
||||
}
|
||||
47
src/components/masterLoading/MasterLoadingComponent.tsx
Normal file
47
src/components/masterLoading/MasterLoadingComponent.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import CircularProgress from 'material-ui/CircularProgress'
|
||||
import Dialog from 'material-ui/Dialog'
|
||||
import { IMasterLoadingComponentProps } from './IMasterLoadingComponentProps'
|
||||
import { IMasterLoadingComponentState } from './IMasterLoadingComponentState'
|
||||
|
||||
// - Import app components
|
||||
|
||||
// - Create MasterLoading component class
|
||||
export default class MasterLoadingComponent extends Component<IMasterLoadingComponentProps,IMasterLoadingComponentState> {
|
||||
|
||||
// Constructor
|
||||
constructor (props: IMasterLoadingComponentProps) {
|
||||
super(props)
|
||||
// Binding functions to `this`
|
||||
|
||||
}
|
||||
|
||||
// Render app DOM component
|
||||
render () {
|
||||
return (
|
||||
<Dialog
|
||||
modal={true}
|
||||
open={this.props.activeLoading}
|
||||
autoDetectWindowHeight={false}
|
||||
overlayStyle={{backgroundColor: 'white'}}
|
||||
contentClassName='mLoading__content'
|
||||
bodyStyle={{backgroundColor: ''}}
|
||||
bodyClassName='mLoading__body'
|
||||
>
|
||||
|
||||
<div>
|
||||
<div className='mLoading__context'>
|
||||
|
||||
<CircularProgress color='white' size={80} thickness={7} />
|
||||
<h1 style={{float: 'right', color: '#fff'}}>Green</h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Dialog>
|
||||
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
2
src/components/masterLoading/index.ts
Normal file
2
src/components/masterLoading/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import MasterLoadingComponent from './MasterLoadingComponent'
|
||||
export default MasterLoadingComponent
|
||||
45
src/components/notify/INotifyComponentProps.ts
Normal file
45
src/components/notify/INotifyComponentProps.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Profile } from 'core/domain/users'
|
||||
import { Notification } from 'core/domain/notifications'
|
||||
|
||||
export interface INotifyComponentProps {
|
||||
|
||||
/**
|
||||
* Notifications
|
||||
*
|
||||
* @type {{[notificationId: string]: Notification}}
|
||||
* @memberof INotifyComponentProps
|
||||
*/
|
||||
notifications?: {[notificationId: string]: Notification}
|
||||
|
||||
/**
|
||||
* Users' profile
|
||||
*
|
||||
* @type {{[userId: string]: Profile}}
|
||||
* @memberof INotifyComponentProps
|
||||
*/
|
||||
info?: {[userId: string]: Profile}
|
||||
|
||||
/**
|
||||
* Close notification
|
||||
*
|
||||
* @memberof INotifyComponentProps
|
||||
*/
|
||||
onRequestClose: () => void
|
||||
|
||||
/**
|
||||
* User notifications popover is opem {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof INotifyComponentProps
|
||||
*/
|
||||
open: boolean
|
||||
|
||||
/**
|
||||
* Keep element
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof INotifyComponentProps
|
||||
*/
|
||||
anchorEl: any
|
||||
|
||||
}
|
||||
4
src/components/notify/INotifyComponentState.ts
Normal file
4
src/components/notify/INotifyComponentState.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface INotifyComponentState {
|
||||
|
||||
}
|
||||
136
src/components/notify/NotifyComponent.tsx
Normal file
136
src/components/notify/NotifyComponent.tsx
Normal file
@@ -0,0 +1,136 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import Popover, { PopoverAnimationVertical } from 'material-ui/Popover'
|
||||
|
||||
// - Import app components
|
||||
import NotifyItem from 'components/notifyItem'
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as userActions from 'actions/userActions'
|
||||
|
||||
import { INotifyComponentProps } from './INotifyComponentProps'
|
||||
import { INotifyComponentState } from './INotifyComponentState'
|
||||
import { Notification } from 'core/domain/notifications'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class NotifyComponent extends Component<INotifyComponentProps,INotifyComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
/**
|
||||
* It will be true if the notification is open
|
||||
*/
|
||||
open: PropTypes.bool,
|
||||
/**
|
||||
* Pass anchor element
|
||||
*/
|
||||
anchorEl: PropTypes.any,
|
||||
/**
|
||||
* Fire to close notificaion
|
||||
*/
|
||||
onRequestClose: PropTypes.func,
|
||||
/**
|
||||
* If user's seen notification box or not (true/false)
|
||||
*/
|
||||
isSeen: PropTypes.bool
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: INotifyComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Defaul state
|
||||
this.state = {
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
|
||||
}
|
||||
|
||||
notifyItemList = () => {
|
||||
let { notifications, info, onRequestClose } = this.props
|
||||
let parsedDOM: any[] = []
|
||||
if (notifications) {
|
||||
Object.keys(notifications).forEach((key) => {
|
||||
const {notifierUserId} = notifications![key]
|
||||
parsedDOM.push(
|
||||
<NotifyItem
|
||||
key={key}
|
||||
description={(notifications![key] ? notifications![key].description || '' : '')}
|
||||
fullName={(info![notifierUserId] ? info![notifierUserId].fullName || '' : '')}
|
||||
avatar={(info![notifierUserId] ? info![notifierUserId].avatar || '' : '')}
|
||||
id={key}
|
||||
isSeen={(notifications![key] ? notifications![key].isSeen || false : false )}
|
||||
url={(notifications![key] ? notifications![key].url || '' : '')}
|
||||
notifierUserId={notifierUserId}
|
||||
closeNotify={onRequestClose}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
return parsedDOM
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
let { open, anchorEl, onRequestClose } = this.props
|
||||
|
||||
return (
|
||||
<Popover
|
||||
className='homeHeader__notify-menu'
|
||||
open={open}
|
||||
anchorEl={anchorEl}
|
||||
anchorOrigin={{ horizontal: 'left', vertical: 'bottom' }}
|
||||
targetOrigin={{ horizontal: 'left', vertical: 'top' }}
|
||||
onRequestClose={onRequestClose}
|
||||
>
|
||||
<div className='container'>
|
||||
<div className='title'>Green </div>
|
||||
<div className='content'>
|
||||
{this.notifyItemList()}
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: INotifyComponentProps) => {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: INotifyComponentProps) => {
|
||||
return {
|
||||
notifications: state.notify.userNotifies,
|
||||
info: state.user.info
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(NotifyComponent as any)
|
||||
2
src/components/notify/index.ts
Normal file
2
src/components/notify/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import NotifyComponent from './NotifyComponent'
|
||||
export default NotifyComponent
|
||||
86
src/components/notifyItem/INotifyItemComponentProps.ts
Normal file
86
src/components/notifyItem/INotifyItemComponentProps.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
export interface INotifyItemComponentProps {
|
||||
|
||||
/**
|
||||
* Notification description
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof INotifyItemComponentProps
|
||||
*/
|
||||
description: string
|
||||
|
||||
/**
|
||||
* User full name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof INotifyItemComponentProps
|
||||
*/
|
||||
fullName: string
|
||||
|
||||
/**
|
||||
* User avatar
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof INotifyItemComponentProps
|
||||
*/
|
||||
avatar: string
|
||||
|
||||
/**
|
||||
* Notification has seen {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof INotifyItemComponentProps
|
||||
*/
|
||||
isSeen: boolean
|
||||
|
||||
/**
|
||||
* Notification identifier
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof INotifyItemComponentProps
|
||||
*/
|
||||
id: string
|
||||
|
||||
/**
|
||||
* Rediret to {url} route
|
||||
*
|
||||
* @memberof INotifyItemComponentProps
|
||||
*/
|
||||
goTo?: (url: string) => any
|
||||
|
||||
/**
|
||||
* Close a notification
|
||||
*
|
||||
* @memberof INotifyItemComponentProps
|
||||
*/
|
||||
closeNotify?: () => void
|
||||
|
||||
/**
|
||||
* Notifier identifier
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof INotifyItemComponentProps
|
||||
*/
|
||||
notifierUserId: string
|
||||
|
||||
/**
|
||||
* The URL which notification mention
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof INotifyItemComponentProps
|
||||
*/
|
||||
url: string
|
||||
|
||||
/**
|
||||
* Delete a notification
|
||||
*
|
||||
* @memberof INotifyItemComponentProps
|
||||
*/
|
||||
deleteNotiy?: (notificationId: string) => any
|
||||
|
||||
/**
|
||||
* Change notification status to has seen
|
||||
*
|
||||
* @memberof INotifyItemComponentProps
|
||||
*/
|
||||
seenNotify?: (notificationId: string) => any
|
||||
}
|
||||
4
src/components/notifyItem/INotifyItemComponentState.ts
Normal file
4
src/components/notifyItem/INotifyItemComponentState.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface INotifyItemComponentState {
|
||||
|
||||
}
|
||||
156
src/components/notifyItem/NotifyItemComponent.tsx
Normal file
156
src/components/notifyItem/NotifyItemComponent.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { push } from 'react-router-redux'
|
||||
import SvgClose from 'material-ui/svg-icons/navigation/close'
|
||||
import { grey400 } from 'material-ui/styles/colors'
|
||||
|
||||
// - Import app components
|
||||
import UserAvatar from 'components/userAvatar'
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as notifyActions from 'actions/notifyActions'
|
||||
|
||||
import { INotifyItemComponentProps } from './INotifyItemComponentProps'
|
||||
import { INotifyItemComponentState } from './INotifyItemComponentState'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class NotifyItemComponent extends Component<INotifyItemComponentProps,INotifyItemComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
/**
|
||||
* Notification description
|
||||
*/
|
||||
description: PropTypes.string,
|
||||
/**
|
||||
* Which user relates to the notification item
|
||||
*/
|
||||
fullName: PropTypes.string,
|
||||
/**
|
||||
* Avatar of the user who relate to the notification item
|
||||
*/
|
||||
avatar: PropTypes.string,
|
||||
/**
|
||||
* Notification identifier
|
||||
*/
|
||||
id: PropTypes.string,
|
||||
/**
|
||||
* If user's seen the notification or not (true/false)
|
||||
*/
|
||||
isSeen: PropTypes.bool,
|
||||
/**
|
||||
* Which address notification refers
|
||||
*/
|
||||
url: PropTypes.string,
|
||||
/**
|
||||
* The notifier user identifier
|
||||
*/
|
||||
notifierUserId: PropTypes.string,
|
||||
/**
|
||||
* Close notification popover
|
||||
*/
|
||||
closeNotify: PropTypes.func
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: INotifyItemComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Defaul state
|
||||
this.state = {
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
this.handleSeenNotify = this.handleSeenNotify.bind(this)
|
||||
}
|
||||
|
||||
handleSeenNotify = (event: any) => {
|
||||
event.preventDefault()
|
||||
const { seenNotify, id, url, goTo, isSeen, closeNotify } = this.props
|
||||
if (id) {
|
||||
if (!isSeen) {
|
||||
seenNotify!(id)
|
||||
}
|
||||
closeNotify!()
|
||||
goTo!(url)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
let { description, fullName, avatar, isSeen, id, goTo,closeNotify, notifierUserId, url, deleteNotiy } = this.props
|
||||
|
||||
return (
|
||||
|
||||
<div className='item' style={isSeen ? { opacity: 0.6 } : {}} key={id}>
|
||||
<div className='avatar'>
|
||||
<NavLink
|
||||
to={`/${notifierUserId}`}
|
||||
onClick={(evt) => {
|
||||
evt.preventDefault()
|
||||
closeNotify!()
|
||||
goTo!(`/${notifierUserId}`)
|
||||
}}
|
||||
>
|
||||
<UserAvatar fullName={fullName} fileName={avatar} />
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className='info'>
|
||||
<NavLink to={url} onClick={this.handleSeenNotify}>
|
||||
<div className='user-name'>
|
||||
{fullName}
|
||||
</div>
|
||||
<div className='description'>
|
||||
{description}
|
||||
</div>
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className='close' onClick={() => deleteNotiy!(id)}>
|
||||
<SvgClose hoverColor={grey400} style={{ cursor: 'pointer' }} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: INotifyItemComponentProps) => {
|
||||
return {
|
||||
goTo: (url: string) => dispatch(push(url)),
|
||||
seenNotify: (id: string) => dispatch(notifyActions.dbSeenNotification(id)),
|
||||
deleteNotiy: (id: string) => dispatch(notifyActions.dbDeleteNotification(id))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: INotifyItemComponentProps) => {
|
||||
return {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(NotifyItemComponent as any)
|
||||
2
src/components/notifyItem/index.ts
Normal file
2
src/components/notifyItem/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import NotifyItemComponent from './NotifyItemComponent'
|
||||
export default NotifyItemComponent
|
||||
32
src/components/people/IPeopleComponentProps.ts
Normal file
32
src/components/people/IPeopleComponentProps.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
export interface IPeopleComponentProps {
|
||||
|
||||
/**
|
||||
* Router match
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof IPeopleComponentProps
|
||||
*/
|
||||
match?: any
|
||||
|
||||
/**
|
||||
* Circles loaded {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IPeopleComponentProps
|
||||
*/
|
||||
circlesLoaded?: boolean
|
||||
|
||||
/**
|
||||
* Rediret to another route
|
||||
*
|
||||
* @memberof IPeopleComponentProps
|
||||
*/
|
||||
goTo?: (url: string) => any
|
||||
|
||||
/**
|
||||
* Set title of top bar
|
||||
*
|
||||
* @memberof IPeopleComponentProps
|
||||
*/
|
||||
setHeaderTitle?: (title: string) => any
|
||||
}
|
||||
4
src/components/people/IPeopleComponentState.ts
Normal file
4
src/components/people/IPeopleComponentState.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface IPeopleComponentState {
|
||||
|
||||
}
|
||||
169
src/components/people/PeopleComponent.tsx
Normal file
169
src/components/people/PeopleComponent.tsx
Normal file
@@ -0,0 +1,169 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import { Tabs, Tab } from 'material-ui/Tabs'
|
||||
import { grey50, grey200, grey400, grey600, cyan500 } from 'material-ui/styles/colors'
|
||||
import { push } from 'react-router-redux'
|
||||
|
||||
// - Import app components
|
||||
import FindPeople from 'components/findPeople'
|
||||
import Following from 'components/following'
|
||||
import Followers from 'components/followers'
|
||||
import YourCircles from 'components/yourCircles'
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as circleActions from 'actions/circleActions'
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import { IPeopleComponentProps } from './IPeopleComponentProps'
|
||||
import { IPeopleComponentState } from './IPeopleComponentState'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class PeopleComponent extends Component<IPeopleComponentProps,IPeopleComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: IPeopleComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Defaul state
|
||||
this.state = {
|
||||
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
|
||||
}
|
||||
|
||||
componentWillMount () {
|
||||
const { setHeaderTitle} = this.props
|
||||
const {tab} = this.props.match.params
|
||||
switch (tab) {
|
||||
case undefined:
|
||||
case '':
|
||||
setHeaderTitle!('People')
|
||||
break
|
||||
case 'circles':
|
||||
setHeaderTitle!('Circles')
|
||||
break
|
||||
case 'followers':
|
||||
setHeaderTitle!('Followers')
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
/**
|
||||
* Component styles
|
||||
*/
|
||||
const styles = {
|
||||
people: {
|
||||
margin: '0 auto',
|
||||
width: '90%'
|
||||
},
|
||||
headline: {
|
||||
fontSize: 24,
|
||||
paddingTop: 16,
|
||||
marginBottom: 12,
|
||||
fontWeight: 400
|
||||
},
|
||||
slide: {
|
||||
padding: 10
|
||||
}
|
||||
}
|
||||
|
||||
const {circlesLoaded, goTo, setHeaderTitle} = this.props
|
||||
const {tab} = this.props.match.params
|
||||
let tabIndex = 0
|
||||
switch (tab) {
|
||||
case undefined:
|
||||
case '':
|
||||
tabIndex = 0
|
||||
break
|
||||
case 'circles':
|
||||
tabIndex = 1
|
||||
break
|
||||
case 'followers':
|
||||
tabIndex = 2
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
return (
|
||||
<div style={styles.people}>
|
||||
<Tabs inkBarStyle={{backgroundColor: grey50}} initialSelectedIndex={tabIndex} >
|
||||
<Tab label='Find People' onActive={() => {
|
||||
goTo!('/people')
|
||||
setHeaderTitle!('People')
|
||||
}} >
|
||||
{circlesLoaded ? <FindPeople /> : ''}
|
||||
</Tab>
|
||||
<Tab label='Following' onActive={() => {
|
||||
goTo!('/people/circles')
|
||||
setHeaderTitle!('Circles')
|
||||
}} >
|
||||
{circlesLoaded ? <Following/> : ''}
|
||||
{circlesLoaded ? <YourCircles/> : ''}
|
||||
</Tab>
|
||||
<Tab label='Followers' onActive={() => {
|
||||
goTo!('/people/followers')
|
||||
setHeaderTitle!('Followers')
|
||||
}}>
|
||||
{circlesLoaded ? <Followers /> : ''}
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IPeopleComponentProps) => {
|
||||
|
||||
return {
|
||||
goTo: (url: string) => dispatch(push(url)),
|
||||
setHeaderTitle : (title: string) => dispatch(globalActions.setHeaderTitle(title))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: IPeopleComponentProps) => {
|
||||
|
||||
return {
|
||||
uid: state.authorize.uid,
|
||||
circlesLoaded: state.circle.loaded
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(PeopleComponent as any))
|
||||
2
src/components/people/index.ts
Normal file
2
src/components/people/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import PeopleComponent from './PeopleComponent'
|
||||
export default PeopleComponent
|
||||
161
src/components/post/IPostComponentProps.ts
Normal file
161
src/components/post/IPostComponentProps.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
export interface IPostComponentProps {
|
||||
|
||||
/**
|
||||
* The context of a post
|
||||
*/
|
||||
body: string
|
||||
/**
|
||||
* The number of comment on a post
|
||||
*/
|
||||
commentCounter: number
|
||||
/**
|
||||
* Creation post date
|
||||
*/
|
||||
creationDate: number
|
||||
/**
|
||||
* Post identifier
|
||||
*/
|
||||
id: string
|
||||
/**
|
||||
* Post image address
|
||||
*/
|
||||
image: string
|
||||
/**
|
||||
* The last time date when post has was edited
|
||||
*/
|
||||
lastEditDate: number
|
||||
/**
|
||||
* The name of the user who created the post
|
||||
*/
|
||||
ownerDisplayName: string
|
||||
/**
|
||||
* The identifier of the user who created the post
|
||||
*/
|
||||
ownerUserId: string
|
||||
/**
|
||||
* The avatar address of the user who created the post
|
||||
* //TODO: User avatar should be as an attribute and [avatar] should be deleted
|
||||
*/
|
||||
ownerAvatar: string
|
||||
/**
|
||||
* The avatar address of the user who created the post
|
||||
*/
|
||||
avatar?: string
|
||||
/**
|
||||
* If post is only [0]text, [1]whith picture, ...
|
||||
*/
|
||||
postTypeId: string
|
||||
/**
|
||||
* The number votes on a post
|
||||
*/
|
||||
score: number
|
||||
/**
|
||||
* Array of tags on a post
|
||||
*/
|
||||
tags: string[]
|
||||
/**
|
||||
* The video address of a post
|
||||
*/
|
||||
video: string
|
||||
/**
|
||||
* If it's true comment will be disabled on a post
|
||||
*/
|
||||
disableComments: boolean
|
||||
/**
|
||||
* If it's true sharing will be disabled on a post
|
||||
*/
|
||||
disableSharing: boolean
|
||||
/**
|
||||
* The number of users who has visited the post
|
||||
*/
|
||||
viewCount: boolean
|
||||
|
||||
/**
|
||||
* User full name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IPostComponentProps
|
||||
*/
|
||||
fullName?: string
|
||||
|
||||
/**
|
||||
* Number of comments on the post
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof IPostComponentProps
|
||||
*/
|
||||
commentCount?: number
|
||||
|
||||
/**
|
||||
* Number of vote on a post
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof IPostComponentProps
|
||||
*/
|
||||
voteCount?: number
|
||||
|
||||
/**
|
||||
* Current user vote the post {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IPostComponentProps
|
||||
*/
|
||||
userVoteStatus?: boolean
|
||||
|
||||
/**
|
||||
* Current user is the owner of the post {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IPostComponentProps
|
||||
*/
|
||||
isPostOwner?: boolean
|
||||
|
||||
/**
|
||||
* Vote a post
|
||||
*
|
||||
* @memberof IPostComponentProps
|
||||
*/
|
||||
vote?: () => any
|
||||
|
||||
/**
|
||||
* Delete a vote on the post
|
||||
*
|
||||
* @memberof IPostComponentProps
|
||||
*/
|
||||
unvote?: () => any
|
||||
|
||||
/**
|
||||
* Delte a post
|
||||
*
|
||||
* @memberof IPostComponentProps
|
||||
*/
|
||||
delete?: (id: string) => any
|
||||
|
||||
/**
|
||||
* Toggle comment disable/enable
|
||||
*
|
||||
* @memberof IPostComponentProps
|
||||
*/
|
||||
toggleDisableComments?: (status: boolean) => any
|
||||
|
||||
/**
|
||||
* Toggle sharing disable/enable
|
||||
*
|
||||
* @memberof IPostComponentProps
|
||||
*/
|
||||
toggleSharingComments?: (status: boolean) => any
|
||||
|
||||
/**
|
||||
* Redirect to {url} route
|
||||
*
|
||||
* @memberof IPostComponentProps
|
||||
*/
|
||||
goTo?: (url: string) => any
|
||||
|
||||
/**
|
||||
* Set tile of top bar
|
||||
*
|
||||
* @memberof IPostComponentProps
|
||||
*/
|
||||
setHomeTitle?: (title: string) => any
|
||||
}
|
||||
42
src/components/post/IPostComponentState.ts
Normal file
42
src/components/post/IPostComponentState.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
export interface IPostComponentState {
|
||||
|
||||
/**
|
||||
* Post text
|
||||
*/
|
||||
text: string
|
||||
/**
|
||||
* It's true if whole the text post is visible
|
||||
*/
|
||||
readMoreState: boolean
|
||||
/**
|
||||
* Handle open comment from parent component
|
||||
*/
|
||||
openComments: boolean
|
||||
/**
|
||||
* If it's true, share dialog will be open
|
||||
*/
|
||||
shareOpen: boolean
|
||||
/**
|
||||
* If it's true comment will be disabled on post
|
||||
*/
|
||||
disableComments: boolean
|
||||
/**
|
||||
* If it's true share will be disabled on post
|
||||
*/
|
||||
disableSharing: boolean
|
||||
/**
|
||||
* Title of share post
|
||||
*/
|
||||
shareTitle: string
|
||||
/**
|
||||
* If it's true, post link will be visible in share post dialog
|
||||
*/
|
||||
openCopyLink: boolean
|
||||
/**
|
||||
* If it's true, post write will be open
|
||||
*/
|
||||
openPostWrite: boolean
|
||||
|
||||
openCommentGroup?: () => void
|
||||
}
|
||||
485
src/components/post/PostComponent.tsx
Normal file
485
src/components/post/PostComponent.tsx
Normal file
@@ -0,0 +1,485 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { push } from 'react-router-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import moment from 'moment'
|
||||
import Linkify from 'react-linkify'
|
||||
|
||||
// - Material UI
|
||||
import { Card, CardActions, CardHeader, CardMedia, CardTitle, CardText } from 'material-ui/Card'
|
||||
import FloatingActionButton from 'material-ui/FloatingActionButton'
|
||||
import SvgShare from 'material-ui/svg-icons/social/share'
|
||||
import SvgLink from 'material-ui/svg-icons/content/link'
|
||||
import SvgComment from 'material-ui/svg-icons/communication/comment'
|
||||
import SvgFavorite from 'material-ui/svg-icons/action/favorite'
|
||||
import SvgFavoriteBorder from 'material-ui/svg-icons/action/favorite-border'
|
||||
import Checkbox from 'material-ui/Checkbox'
|
||||
import FlatButton from 'material-ui/FlatButton'
|
||||
import Divider from 'material-ui/Divider'
|
||||
import { grey200, grey400, grey600 } from 'material-ui/styles/colors'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import Menu from 'material-ui/Menu'
|
||||
import MenuItem from 'material-ui/MenuItem'
|
||||
import TextField from 'material-ui/TextField'
|
||||
import Dialog from 'material-ui/Dialog'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import MoreVertIcon from 'material-ui/svg-icons/navigation/more-vert'
|
||||
import IconMenu from 'material-ui/IconMenu'
|
||||
import reactStringReplace from 'react-string-replace'
|
||||
|
||||
// - Import app components
|
||||
import CommentGroup from 'components/commentGroup'
|
||||
import PostWrite from 'components/postWrite'
|
||||
import Img from 'components/img'
|
||||
import IconButtonElement from 'layouts/IconButtonElement'
|
||||
import UserAvatar from 'components/userAvatar'
|
||||
|
||||
// - Import actions
|
||||
import * as voteActions from 'actions/voteActions'
|
||||
import * as postActions from 'actions/postActions'
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import { IPostComponentProps } from './IPostComponentProps'
|
||||
import { IPostComponentState } from './IPostComponentState'
|
||||
|
||||
// - Create component class
|
||||
export class PostComponent extends Component<IPostComponentProps,IPostComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
|
||||
/**
|
||||
* The context of a post
|
||||
*/
|
||||
body: PropTypes.string,
|
||||
/**
|
||||
* The number of comment on a post
|
||||
*/
|
||||
commentCounter: PropTypes.number,
|
||||
/**
|
||||
* Creation post date
|
||||
*/
|
||||
creationDate: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
]),
|
||||
/**
|
||||
* Post identifier
|
||||
*/
|
||||
id: PropTypes.string,
|
||||
/**
|
||||
* Post image address
|
||||
*/
|
||||
image: PropTypes.string,
|
||||
/**
|
||||
* The last time date when post has was edited
|
||||
*/
|
||||
lastEditDate: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number
|
||||
]),
|
||||
/**
|
||||
* The name of the user who created the post
|
||||
*/
|
||||
ownerDisplayName: PropTypes.string,
|
||||
/**
|
||||
* The identifier of the user who created the post
|
||||
*/
|
||||
ownerUserId: PropTypes.string,
|
||||
/**
|
||||
* The avatar address of the user who created the post
|
||||
*/
|
||||
ownerAvatar: PropTypes.string,
|
||||
/**
|
||||
* If post is only [0]text, [1]whith picture, ...
|
||||
*/
|
||||
postTypeId: PropTypes.number,
|
||||
/**
|
||||
* The number votes on a post
|
||||
*/
|
||||
score: PropTypes.number,
|
||||
/**
|
||||
* Array of tags on a post
|
||||
*/
|
||||
tags: PropTypes.array,
|
||||
/**
|
||||
* The video address of a post
|
||||
*/
|
||||
video: PropTypes.string,
|
||||
/**
|
||||
* If it's true comment will be disabled on a post
|
||||
*/
|
||||
disableComments: PropTypes.bool,
|
||||
/**
|
||||
* If it's true sharing will be disabled on a post
|
||||
*/
|
||||
disableSharing: PropTypes.bool,
|
||||
/**
|
||||
* The number of users who has visited the post
|
||||
*/
|
||||
viewCount: PropTypes.number
|
||||
}
|
||||
|
||||
styles = {
|
||||
counter: {
|
||||
lineHeight: '36px',
|
||||
color: '#777',
|
||||
fontSize: '12px',
|
||||
marginRight: '6px'
|
||||
},
|
||||
postBody: {
|
||||
wordWrap: 'break-word'
|
||||
},
|
||||
dialog: {
|
||||
width: '',
|
||||
maxWidth: '530px',
|
||||
borderRadius: '4px'
|
||||
},
|
||||
rightIconMenu: {
|
||||
position: 'absolute',
|
||||
right: 18,
|
||||
top: 8
|
||||
},
|
||||
iconButton: {
|
||||
width: 24,
|
||||
height: 24
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: IPostComponentProps) {
|
||||
super(props)
|
||||
this.state = {
|
||||
/**
|
||||
* Post text
|
||||
*/
|
||||
text: this.props.body,
|
||||
/**
|
||||
* It's true if whole the text post is visible
|
||||
*/
|
||||
readMoreState: false,
|
||||
/**
|
||||
* Handle open comment from parent component
|
||||
*/
|
||||
openComments: false,
|
||||
/**
|
||||
* If it's true, share dialog will be open
|
||||
*/
|
||||
shareOpen: false,
|
||||
/**
|
||||
* If it's true comment will be disabled on post
|
||||
*/
|
||||
disableComments: this.props.disableComments,
|
||||
/**
|
||||
* If it's true share will be disabled on post
|
||||
*/
|
||||
disableSharing: this.props.disableSharing,
|
||||
/**
|
||||
* Title of share post
|
||||
*/
|
||||
shareTitle: 'Share On',
|
||||
/**
|
||||
* If it's true, post link will be visible in share post dialog
|
||||
*/
|
||||
openCopyLink: false,
|
||||
/**
|
||||
* If it's true, post write will be open
|
||||
*/
|
||||
openPostWrite: false
|
||||
}
|
||||
|
||||
// Binding functions to this
|
||||
this.handleReadMore = this.handleReadMore.bind(this)
|
||||
this.getOpenCommentGroup = this.getOpenCommentGroup.bind(this)
|
||||
this.handleVote = this.handleVote.bind(this)
|
||||
this.handleOpenShare = this.handleOpenShare.bind(this)
|
||||
this.handleCloseShare = this.handleCloseShare.bind(this)
|
||||
this.handleCopyLink = this.handleCopyLink.bind(this)
|
||||
this.handleDelete = this.handleDelete.bind(this)
|
||||
this.handleOpenPostWrite = this.handleOpenPostWrite.bind(this)
|
||||
this.handleClosePostWrite = this.handleClosePostWrite.bind(this)
|
||||
this.handleOpenComments = this.handleOpenComments.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle on show/hide comment
|
||||
* @param {event} evt passed by clicking on comment slide show
|
||||
*/
|
||||
handleOpenComments = () => {
|
||||
this.setState({
|
||||
openComments: !this.state.openComments
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Open post write
|
||||
*
|
||||
*
|
||||
* @memberof StreamComponent
|
||||
*/
|
||||
handleOpenPostWrite = () => {
|
||||
this.setState({
|
||||
openPostWrite: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Close post write
|
||||
*
|
||||
*
|
||||
* @memberof StreamComponent
|
||||
*/
|
||||
handleClosePostWrite = () => {
|
||||
this.setState({
|
||||
openPostWrite: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a post
|
||||
*
|
||||
*
|
||||
* @memberof Post
|
||||
*/
|
||||
handleDelete = () => {
|
||||
this.props.delete!(this.props.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* Show copy link
|
||||
*
|
||||
*
|
||||
* @memberof Post
|
||||
*/
|
||||
handleCopyLink = () => {
|
||||
this.setState({
|
||||
openCopyLink: true,
|
||||
shareTitle: 'Copy Link'
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Open share post
|
||||
*
|
||||
*
|
||||
* @memberof Post
|
||||
*/
|
||||
handleOpenShare = () => {
|
||||
this.setState({
|
||||
shareOpen: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Close share post
|
||||
*
|
||||
*
|
||||
* @memberof Post
|
||||
*/
|
||||
handleCloseShare = () => {
|
||||
this.setState({
|
||||
shareOpen: false,
|
||||
shareTitle: 'Share On',
|
||||
openCopyLink: false
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle vote on a post
|
||||
*
|
||||
*
|
||||
* @memberof Post
|
||||
*/
|
||||
handleVote = () => {
|
||||
if (this.props.userVoteStatus) {
|
||||
this.props.unvote!()
|
||||
} else {
|
||||
this.props.vote!()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set open comment group function on state which passed by CommentGroup component
|
||||
* @param {function} open the function to open comment list
|
||||
*/
|
||||
getOpenCommentGroup = (open: () => void) => {
|
||||
this.setState({
|
||||
openCommentGroup: open
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle read more event
|
||||
* @param {event} evt is the event passed by click on read more
|
||||
*/
|
||||
handleReadMore (event: any) {
|
||||
this.setState({
|
||||
readMoreState: !this.state.readMoreState
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
|
||||
const RightIconMenu = () => (
|
||||
<IconMenu iconButtonElement={IconButtonElement} style={{ display: 'block', position: 'absolute', top: '0px', right: '4px' }}>
|
||||
<MenuItem primaryText='Edit' onClick={this.handleOpenPostWrite} />
|
||||
<MenuItem primaryText='Delete' onClick={this.handleDelete} />
|
||||
<MenuItem primaryText={this.props.disableComments ? 'Enable comments' : 'Disable comments'} onClick={() => this.props.toggleDisableComments!(!this.props.disableComments)} />
|
||||
<MenuItem primaryText={this.props.disableSharing ? 'Enable sharing' : 'Disable sharing'} onClick={() => this.props.toggleSharingComments!(!this.props.disableSharing)} />
|
||||
</IconMenu>
|
||||
)
|
||||
|
||||
const {ownerUserId,setHomeTitle, goTo, ownerDisplayName,creationDate, avatar, fullName, isPostOwner,image, body} = this.props
|
||||
// Define variables
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader
|
||||
title={<NavLink to={`/${ownerUserId}`}>{ownerDisplayName}</NavLink>}
|
||||
subtitle={moment.unix(creationDate).fromNow() + ' | public'}
|
||||
avatar={<NavLink to={`/${ownerUserId}`}><UserAvatar fullName={fullName!} fileName={avatar!} size={36} /></NavLink>}
|
||||
>
|
||||
{isPostOwner ? ( <div style={this.styles.rightIconMenu as any}><RightIconMenu /></div>) : ''}
|
||||
</CardHeader>
|
||||
{image ? (
|
||||
<CardMedia>
|
||||
<Img fileName={image} />
|
||||
</CardMedia>) : ''}
|
||||
|
||||
<CardText style={this.styles.postBody}>
|
||||
<Linkify properties={{target: '_blank', style: {color: 'blue'}}}>
|
||||
{reactStringReplace(body,/#(\w+)/g, (match: string, i: string) => (
|
||||
<NavLink
|
||||
style={{color: 'green'}}
|
||||
key={match + i}
|
||||
to={`/tag/${match}`}
|
||||
onClick ={evt => {
|
||||
evt.preventDefault()
|
||||
goTo!(`/tag/${match}`)
|
||||
setHomeTitle!(`#${match}`)
|
||||
}}
|
||||
>
|
||||
#{match}
|
||||
|
||||
</NavLink>
|
||||
|
||||
))}
|
||||
</Linkify>
|
||||
</CardText>
|
||||
<CardActions>
|
||||
<div style={{ margin: '16px 8px', display: 'flex', justifyContent: 'space-between' }}>
|
||||
<div style={{ display: 'flex' }}>
|
||||
{/*<FloatingActionButton style={{ margin: "0 8px" }} zDepth={1} backgroundColor={grey200} iconStyle={{ color: grey600, fill: grey600, height: "36px", width: "36px" }} zDepth={1} secondary={false}>*/}
|
||||
<div className='g__circle' onClick={this.handleVote}>
|
||||
<Checkbox
|
||||
checkedIcon={<SvgFavorite style={{fill: '#4CAF50'}}/>}
|
||||
uncheckedIcon={<SvgFavoriteBorder style={{fill: '#757575'}} />}
|
||||
defaultChecked={this.props.userVoteStatus}
|
||||
style={{transform: 'translate(6px, 6px)'}}
|
||||
/>
|
||||
</div>
|
||||
<div style={this.styles.counter}> {this.props.voteCount! > 0 ? this.props.voteCount : ''} </div>
|
||||
</div>
|
||||
<div style={{ display: 'flex' }}>
|
||||
{!this.props.disableComments ? (<div style={{display: 'inherit'}}><FloatingActionButton onClick={this.handleOpenComments} style={{ margin: '0 8px' }} backgroundColor={grey200} iconStyle={{ color: grey600, fill: grey600, height: '36px', width: '36px' }} zDepth={1} secondary={false}>
|
||||
<SvgComment viewBox='0 -9 24 34' style={{ height: '30px', width: '30px' }} /> 3
|
||||
</FloatingActionButton>
|
||||
<div style={this.styles.counter}>{this.props.commentCount! > 0 ? this.props.commentCount : ''} </div></div>) : ''}
|
||||
{!this.props.disableSharing ? (<FloatingActionButton onClick={this.handleOpenShare} style={{ margin: '0 8px' }} backgroundColor={grey200} iconStyle={{ color: grey600, fill: grey600, height: '36px', width: '36px' }} zDepth={1} secondary={false}>
|
||||
<SvgShare viewBox='0 -9 24 34' style={{ height: '30px', width: '30px' }} />
|
||||
</FloatingActionButton>) : ''}
|
||||
</div>
|
||||
</div>
|
||||
</CardActions>
|
||||
|
||||
<CommentGroup open={this.state.openComments} ownerPostUserId={this.props.ownerUserId} onToggleRequest={this.handleOpenComments} isPostOwner={this.props.isPostOwner!} disableComments={this.props.disableComments} postId={this.props.id} />
|
||||
|
||||
{/* Copy link dialog*/}
|
||||
<Dialog
|
||||
title='Share On'
|
||||
modal={false}
|
||||
open={this.state.shareOpen}
|
||||
onRequestClose={this.handleCloseShare}
|
||||
overlayStyle={{ background: 'rgba(0,0,0,0.12)' }}
|
||||
contentStyle={this.styles.dialog}
|
||||
autoDetectWindowHeight={false}
|
||||
actionsContainerStyle={{ borderTop: '1px solid rgb(224, 224, 224)' }}
|
||||
>
|
||||
{!this.state.openCopyLink
|
||||
? (<Paper >
|
||||
<Menu>
|
||||
<MenuItem primaryText='Copy Link' leftIcon={<SvgLink />} onClick={this.handleCopyLink} />
|
||||
</Menu>
|
||||
</Paper>)
|
||||
: <TextField fullWidth={true} id='text-field-default' defaultValue={`${location.origin}/${this.props.ownerUserId}/posts/${this.props.id}`} />
|
||||
}
|
||||
</Dialog>
|
||||
|
||||
<PostWrite
|
||||
open={this.state.openPostWrite}
|
||||
onRequestClose={this.handleClosePostWrite}
|
||||
edit={true}
|
||||
text= {this.props.body}
|
||||
image= {this.props.image ? this.props.image : ''}
|
||||
id= {this.props.id}
|
||||
disableComments= {this.props.disableComments}
|
||||
disableSharing= {this.props.disableSharing}
|
||||
/>
|
||||
|
||||
</Card>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IPostComponentProps) => {
|
||||
return {
|
||||
vote: () => dispatch(voteActions.dbAddVote(ownProps.id,ownProps.ownerUserId)),
|
||||
unvote: () => dispatch(voteActions.dbDeleteVote(ownProps.id)) ,
|
||||
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)),
|
||||
goTo: (url: string) => dispatch(push(url)),
|
||||
setHomeTitle: (title: string) => dispatch(globalActions.setHeaderTitle(title || ''))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: 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)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PostComponent as any)
|
||||
2
src/components/post/index.ts
Normal file
2
src/components/post/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import PostComponent from './PostComponent'
|
||||
export default PostComponent
|
||||
28
src/components/postPage/IPostPageComponentProps.ts
Normal file
28
src/components/postPage/IPostPageComponentProps.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Post } from 'core/domain/posts'
|
||||
|
||||
export interface IPostPageComponentProps {
|
||||
|
||||
/**
|
||||
* Load the post
|
||||
*
|
||||
* @memberof IPostPageComponentProps
|
||||
*/
|
||||
loadPost?: () => any
|
||||
|
||||
/**
|
||||
* Load user profile
|
||||
*
|
||||
* @memberof IPostPageComponentProps
|
||||
*/
|
||||
loadUserInfo?: () => any
|
||||
|
||||
/**
|
||||
* Route match
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof IPostPageComponentProps
|
||||
*/
|
||||
match?: any
|
||||
|
||||
posts: {[postId: string]: Post}
|
||||
}
|
||||
4
src/components/postPage/IPostPageComponentState.ts
Normal file
4
src/components/postPage/IPostPageComponentState.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
export interface IPostPageComponentState {
|
||||
|
||||
}
|
||||
88
src/components/postPage/PostPageComponent.tsx
Normal file
88
src/components/postPage/PostPageComponent.tsx
Normal file
@@ -0,0 +1,88 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
// - Import app components
|
||||
import Stream from 'components/stream'
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as postActions from 'actions/postActions'
|
||||
import * as userActions from 'actions/userActions'
|
||||
|
||||
import { IPostPageComponentProps } from './IPostPageComponentProps'
|
||||
import { IPostPageComponentState } from './IPostPageComponentState'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
export class PostPageComponent extends Component<IPostPageComponentProps,IPostPageComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: IPostPageComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Defaul state
|
||||
this.state = {
|
||||
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
|
||||
}
|
||||
componentWillMount () {
|
||||
this.props.loadPost!()
|
||||
this.props.loadUserInfo!()
|
||||
}
|
||||
|
||||
/**
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
return (
|
||||
<Stream posts={this.props.posts} displayWriting={false} />
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map dispatch to props
|
||||
* @param {func} dispatch is the function to dispatch action to reducers
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapDispatchToProps = (dispatch: any,ownProps: IPostPageComponentProps) => {
|
||||
const {userId,postId} = ownProps.match.params
|
||||
return{
|
||||
loadPost: () => dispatch(postActions.dbGetPostById(userId,postId)),
|
||||
loadUserInfo: () => dispatch(userActions.dbGetUserInfoByUserId(userId,'header'))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Map state to props
|
||||
* @param {object} state is the obeject from redux store
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any,ownProps: IPostPageComponentProps) => {
|
||||
const {userId,postId} = ownProps.match.params
|
||||
return{
|
||||
avatar: state.user.info && state.user.info[userId] ? state.user.info[userId].avatar : '',
|
||||
name: state.user.info && state.user.info[userId] ? state.user.info[userId].fullName : '',
|
||||
posts: state.post.userPosts && state.post.userPosts[userId] ? {[postId] : { ...state.post.userPosts[userId][postId]}} : {}
|
||||
}
|
||||
}
|
||||
|
||||
// - Connect component to redux store
|
||||
export default connect(mapStateToProps,mapDispatchToProps)(PostPageComponent as any)
|
||||
2
src/components/postPage/index.ts
Normal file
2
src/components/postPage/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
import PostPageComponent from './PostPageComponent'
|
||||
export default PostPageComponent
|
||||
95
src/components/postWrite/IPostWriteComponentProps.ts
Normal file
95
src/components/postWrite/IPostWriteComponentProps.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { Post } from 'core/domain/posts'
|
||||
|
||||
export interface IPostWriteComponentProps {
|
||||
|
||||
/**
|
||||
* If it's true post writing page will be open
|
||||
*/
|
||||
open: boolean
|
||||
/**
|
||||
* Recieve request close function
|
||||
*/
|
||||
onRequestClose: () => void
|
||||
/**
|
||||
* Post write style
|
||||
*/
|
||||
style?: {}
|
||||
/**
|
||||
* If it's true, post will be in edit view
|
||||
*/
|
||||
edit?: boolean
|
||||
/**
|
||||
* The text of post in editing state
|
||||
*/
|
||||
text?: string
|
||||
/**
|
||||
* The image of post in editing state
|
||||
*/
|
||||
image?: string
|
||||
/**
|
||||
* If post state is editing this id sould be filled with post identifier
|
||||
*/
|
||||
id?: string
|
||||
|
||||
/**
|
||||
* The post has image {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IPostWriteComponentProps
|
||||
*/
|
||||
postImageState?: boolean
|
||||
|
||||
/**
|
||||
* User avatar address
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IPostWriteComponentProps
|
||||
*/
|
||||
avatar?: string
|
||||
|
||||
/**
|
||||
* User name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IPostWriteComponentProps
|
||||
*/
|
||||
name?: string
|
||||
|
||||
/**
|
||||
* Post image full path
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IPostWriteComponentProps
|
||||
*/
|
||||
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
|
||||
|
||||
/**
|
||||
* Save a post
|
||||
*
|
||||
* @memberof IPostWriteComponentProps
|
||||
*/
|
||||
post?: (post: Post, callback: Function) => any
|
||||
|
||||
/**
|
||||
* Update a post
|
||||
*
|
||||
* @memberof IPostWriteComponentProps
|
||||
*/
|
||||
update?: (post: Post, callback: Function) => any
|
||||
}
|
||||
33
src/components/postWrite/IPostWriteComponentState.ts
Normal file
33
src/components/postWrite/IPostWriteComponentState.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
|
||||
export interface IPostWriteComponentState {
|
||||
|
||||
/**
|
||||
* Post text
|
||||
*/
|
||||
postText: string
|
||||
/**
|
||||
* The URL image of the post
|
||||
*/
|
||||
image?: string
|
||||
/**
|
||||
* The path identifier of image on the server
|
||||
*/
|
||||
imageFullPath: string
|
||||
/**
|
||||
* If it's true gallery will be open
|
||||
*/
|
||||
galleryOpen: boolean
|
||||
/**
|
||||
* If it's true post button will be disabled
|
||||
*/
|
||||
disabledPost: boolean
|
||||
/**
|
||||
* If it's true comment will be disabled on post
|
||||
*/
|
||||
disableComments: boolean
|
||||
/**
|
||||
* If it's true share will be disabled on post
|
||||
*/
|
||||
disableSharing: boolean
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user