From 7c691d8e4d84f7414e6b43be4ff427fa18afea54 Mon Sep 17 00:00:00 2001 From: Qolzam Date: Thu, 6 Jul 2017 11:20:18 +0430 Subject: [PATCH] Initial git --- .gitignore | 4 + LICENSE | 19 + _config.yml | 1 + app/actions/authorizeActions.jsx | 138 ++++++ app/actions/circleActions.jsx | 337 +++++++++++++ app/actions/commentActions.jsx | 231 +++++++++ app/actions/fileActions.jsx | 52 ++ app/actions/globalActions.jsx | 218 +++++++++ app/actions/imageGalleryActions.jsx | 248 ++++++++++ app/actions/imageUploaderActions.jsx | 20 + app/actions/notifyActions.jsx | 168 +++++++ app/actions/postActions.jsx | 339 ++++++++++++++ app/actions/userActions.jsx | 192 ++++++++ app/actions/voteActions.jsx | 130 +++++ app/api/AuthAPI.jsx | 16 + app/api/AuthRouterAPI.jsx | 30 ++ app/api/CircleAPI.jsx | 51 ++ app/api/FileAPI.jsx | 189 ++++++++ app/api/PostAPI.jsx | 29 ++ app/app.jsx | 56 +++ app/components/Blog.jsx | 271 +++++++++++ app/components/Circle.jsx | 266 +++++++++++ app/components/Comment.jsx | 314 +++++++++++++ app/components/CommentGroup.jsx | 270 +++++++++++ app/components/CommentList.jsx | 133 ++++++ app/components/CommentWrite.jsx | 84 ++++ app/components/EditProfile.jsx | 458 ++++++++++++++++++ app/components/FindPeople.jsx | 110 +++++ app/components/Followers.jsx | 89 ++++ app/components/Following.jsx | 95 ++++ app/components/Home.jsx | 202 ++++++++ app/components/HomeHeader.jsx | 285 +++++++++++ app/components/ImageGallery.jsx | 246 ++++++++++ app/components/Img.jsx | 158 +++++++ app/components/ImgCover.jsx | 191 ++++++++ app/components/Login.jsx | 212 +++++++++ app/components/Master.jsx | 216 +++++++++ app/components/MasterLoading.jsx | 53 +++ app/components/Notify.jsx | 139 ++++++ app/components/NotifyItem.jsx | 155 ++++++ app/components/People.jsx | 174 +++++++ app/components/Post.jsx | 495 ++++++++++++++++++++ app/components/PostPage.jsx | 89 ++++ app/components/PostWrite.jsx | 484 +++++++++++++++++++ app/components/Profile.jsx | 165 +++++++ app/components/ProfileHead.jsx | 246 ++++++++++ app/components/Settings.jsx | 223 +++++++++ app/components/Sidebar.jsx | 296 ++++++++++++ app/components/SidebarContent.jsx | 54 +++ app/components/SidebarMain.jsx | 36 ++ app/components/Signup.jsx | 271 +++++++++++ app/components/UserAvatar.jsx | 129 +++++ app/components/UserBox.jsx | 293 ++++++++++++ app/components/UserBoxList.jsx | 102 ++++ app/components/YourCircles.jsx | 113 +++++ app/components/pattern | 63 +++ app/constants/actionTypes.jsx | 97 ++++ app/firebase/index.js | 26 + app/helpers/index.jsx | 29 ++ app/layouts/DialogTitle.jsx | 95 ++++ app/layouts/IconButtonElement.jsx | 32 ++ app/layouts/IconMenuElement.jsx | 0 app/reducers/authorizeReducer.jsx | 52 ++ app/reducers/circleReducer.jsx | 166 +++++++ app/reducers/commentReducer.jsx | 127 +++++ app/reducers/fileReducer.jsx | 47 ++ app/reducers/globalReducer.jsx | 127 +++++ app/reducers/imageGalleryReducer.jsx | 95 ++++ app/reducers/imageUploaderReducer.jsx | 44 ++ app/reducers/notifyReducer.jsx | 81 ++++ app/reducers/postReducer.jsx | 105 +++++ app/reducers/userReducer.jsx | 81 ++++ app/reducers/voteReducer.jsx | 83 ++++ app/store/configureStore.jsx | 56 +++ app/styles/app.scss | 23 + app/styles/base/_animate.scss | 167 +++++++ app/styles/base/_grid.scss | 143 ++++++ app/styles/base/_variables.scss | 20 + app/styles/components/_blog.scss | 11 + app/styles/components/_comment.scss | 39 ++ app/styles/components/_global.scss | 199 ++++++++ app/styles/components/_home.scss | 11 + app/styles/components/_homeHeader.scss | 134 ++++++ app/styles/components/_imageGallery.scss | 0 app/styles/components/_login.scss | 4 + app/styles/components/_master.scss | 39 ++ app/styles/components/_masterLoading.scss | 12 + app/styles/components/_people.scss | 24 + app/styles/components/_post.scss | 0 app/styles/components/_postWrite.scss | 0 app/styles/components/_profile.scss | 121 +++++ app/styles/components/_settings.scss | 3 + app/styles/components/_sidebar.scss | 35 ++ app/styles/components/_signup.scss | 3 + app/tests/actions/authorizeActions.test.jsx | 34 ++ app/tests/app.test.jsx | 7 + app/tests/components/Blog.test.jsx | 14 + app/tests/components/Circle.test.jsx | 14 + app/tests/components/Comment.test.jsx | 14 + app/tests/components/CommentWrite.test.jsx | 14 + app/tests/components/CommnetGroup.test.jsx | 14 + app/tests/components/CommnetList.jsx | 14 + app/tests/components/EditProfile.test.jsx | 14 + app/tests/components/FindPeople.test.jsx | 14 + app/tests/components/Followers.test.jsx | 14 + app/tests/components/Following.test.jsx | 14 + app/tests/components/Home.test.jsx | 14 + app/tests/components/HomeHeader.test.jsx | 14 + app/tests/components/ImageGallery.test.jsx | 14 + app/tests/components/Img.test.jsx | 14 + app/tests/components/ImgCover.test.jsx | 14 + app/tests/components/Login.test.jsx | 14 + app/tests/components/Master.test.jsx | 14 + app/tests/components/NotifyItem.test.jsx | 14 + app/tests/components/Post.test.jsx | 14 + app/tests/components/PostWrite.test.jsx | 14 + app/tests/components/Settings.test.jsx | 14 + app/tests/components/Sidebar.test.jsx | 14 + app/tests/components/Signup.test.jsx | 14 + app/tests/components/UserBox.test.jsx | 14 + app/tests/components/UserBoxList.test.jsx | 14 + app/tests/components/YourCircles.test.jsx | 14 + book.json | 19 + docs/LICENSE | 19 + docs/README.md | 73 +++ docs/SUMMARY.md | 15 + docs/layers/actions.md | 0 docs/layers/api.md | 0 docs/layers/app.md | 0 docs/layers/components.md | 0 docs/layers/reducers.md | 0 docs/layers/store.md | 0 docs/methods.md | 30 ++ docs/motivation.md | 0 karma.conf.js | 26 + package.json | 76 +++ playground/moment-examples.js | 16 + public/images/flags.png | Bin 0 -> 28123 bytes public/index.html | 131 ++++++ readme.md | 84 ++++ server.js | 39 ++ webpack.config.js | 134 ++++++ 142 files changed, 13046 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 _config.yml create mode 100644 app/actions/authorizeActions.jsx create mode 100644 app/actions/circleActions.jsx create mode 100644 app/actions/commentActions.jsx create mode 100644 app/actions/fileActions.jsx create mode 100644 app/actions/globalActions.jsx create mode 100644 app/actions/imageGalleryActions.jsx create mode 100644 app/actions/imageUploaderActions.jsx create mode 100644 app/actions/notifyActions.jsx create mode 100644 app/actions/postActions.jsx create mode 100644 app/actions/userActions.jsx create mode 100644 app/actions/voteActions.jsx create mode 100644 app/api/AuthAPI.jsx create mode 100644 app/api/AuthRouterAPI.jsx create mode 100644 app/api/CircleAPI.jsx create mode 100644 app/api/FileAPI.jsx create mode 100644 app/api/PostAPI.jsx create mode 100644 app/app.jsx create mode 100644 app/components/Blog.jsx create mode 100644 app/components/Circle.jsx create mode 100644 app/components/Comment.jsx create mode 100644 app/components/CommentGroup.jsx create mode 100644 app/components/CommentList.jsx create mode 100644 app/components/CommentWrite.jsx create mode 100644 app/components/EditProfile.jsx create mode 100644 app/components/FindPeople.jsx create mode 100644 app/components/Followers.jsx create mode 100644 app/components/Following.jsx create mode 100644 app/components/Home.jsx create mode 100644 app/components/HomeHeader.jsx create mode 100644 app/components/ImageGallery.jsx create mode 100644 app/components/Img.jsx create mode 100644 app/components/ImgCover.jsx create mode 100644 app/components/Login.jsx create mode 100644 app/components/Master.jsx create mode 100644 app/components/MasterLoading.jsx create mode 100644 app/components/Notify.jsx create mode 100644 app/components/NotifyItem.jsx create mode 100644 app/components/People.jsx create mode 100644 app/components/Post.jsx create mode 100644 app/components/PostPage.jsx create mode 100644 app/components/PostWrite.jsx create mode 100644 app/components/Profile.jsx create mode 100644 app/components/ProfileHead.jsx create mode 100644 app/components/Settings.jsx create mode 100644 app/components/Sidebar.jsx create mode 100644 app/components/SidebarContent.jsx create mode 100644 app/components/SidebarMain.jsx create mode 100644 app/components/Signup.jsx create mode 100644 app/components/UserAvatar.jsx create mode 100644 app/components/UserBox.jsx create mode 100644 app/components/UserBoxList.jsx create mode 100644 app/components/YourCircles.jsx create mode 100644 app/components/pattern create mode 100644 app/constants/actionTypes.jsx create mode 100644 app/firebase/index.js create mode 100644 app/helpers/index.jsx create mode 100644 app/layouts/DialogTitle.jsx create mode 100644 app/layouts/IconButtonElement.jsx create mode 100644 app/layouts/IconMenuElement.jsx create mode 100644 app/reducers/authorizeReducer.jsx create mode 100644 app/reducers/circleReducer.jsx create mode 100644 app/reducers/commentReducer.jsx create mode 100644 app/reducers/fileReducer.jsx create mode 100644 app/reducers/globalReducer.jsx create mode 100644 app/reducers/imageGalleryReducer.jsx create mode 100644 app/reducers/imageUploaderReducer.jsx create mode 100644 app/reducers/notifyReducer.jsx create mode 100644 app/reducers/postReducer.jsx create mode 100644 app/reducers/userReducer.jsx create mode 100644 app/reducers/voteReducer.jsx create mode 100644 app/store/configureStore.jsx create mode 100644 app/styles/app.scss create mode 100644 app/styles/base/_animate.scss create mode 100644 app/styles/base/_grid.scss create mode 100644 app/styles/base/_variables.scss create mode 100644 app/styles/components/_blog.scss create mode 100644 app/styles/components/_comment.scss create mode 100644 app/styles/components/_global.scss create mode 100644 app/styles/components/_home.scss create mode 100644 app/styles/components/_homeHeader.scss create mode 100644 app/styles/components/_imageGallery.scss create mode 100644 app/styles/components/_login.scss create mode 100644 app/styles/components/_master.scss create mode 100644 app/styles/components/_masterLoading.scss create mode 100644 app/styles/components/_people.scss create mode 100644 app/styles/components/_post.scss create mode 100644 app/styles/components/_postWrite.scss create mode 100644 app/styles/components/_profile.scss create mode 100644 app/styles/components/_settings.scss create mode 100644 app/styles/components/_sidebar.scss create mode 100644 app/styles/components/_signup.scss create mode 100644 app/tests/actions/authorizeActions.test.jsx create mode 100644 app/tests/app.test.jsx create mode 100644 app/tests/components/Blog.test.jsx create mode 100644 app/tests/components/Circle.test.jsx create mode 100644 app/tests/components/Comment.test.jsx create mode 100644 app/tests/components/CommentWrite.test.jsx create mode 100644 app/tests/components/CommnetGroup.test.jsx create mode 100644 app/tests/components/CommnetList.jsx create mode 100644 app/tests/components/EditProfile.test.jsx create mode 100644 app/tests/components/FindPeople.test.jsx create mode 100644 app/tests/components/Followers.test.jsx create mode 100644 app/tests/components/Following.test.jsx create mode 100644 app/tests/components/Home.test.jsx create mode 100644 app/tests/components/HomeHeader.test.jsx create mode 100644 app/tests/components/ImageGallery.test.jsx create mode 100644 app/tests/components/Img.test.jsx create mode 100644 app/tests/components/ImgCover.test.jsx create mode 100644 app/tests/components/Login.test.jsx create mode 100644 app/tests/components/Master.test.jsx create mode 100644 app/tests/components/NotifyItem.test.jsx create mode 100644 app/tests/components/Post.test.jsx create mode 100644 app/tests/components/PostWrite.test.jsx create mode 100644 app/tests/components/Settings.test.jsx create mode 100644 app/tests/components/Sidebar.test.jsx create mode 100644 app/tests/components/Signup.test.jsx create mode 100644 app/tests/components/UserBox.test.jsx create mode 100644 app/tests/components/UserBoxList.test.jsx create mode 100644 app/tests/components/YourCircles.test.jsx create mode 100644 book.json create mode 100644 docs/LICENSE create mode 100644 docs/README.md create mode 100644 docs/SUMMARY.md create mode 100644 docs/layers/actions.md create mode 100644 docs/layers/api.md create mode 100644 docs/layers/app.md create mode 100644 docs/layers/components.md create mode 100644 docs/layers/reducers.md create mode 100644 docs/layers/store.md create mode 100644 docs/methods.md create mode 100644 docs/motivation.md create mode 100644 karma.conf.js create mode 100644 package.json create mode 100644 playground/moment-examples.js create mode 100644 public/images/flags.png create mode 100644 public/index.html create mode 100644 readme.md create mode 100644 server.js create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93e04a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +public/bundle.js +config/ +.vscode/ \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f11fc6e --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2017 Amir Movahedi + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..c419263 --- /dev/null +++ b/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-cayman \ No newline at end of file diff --git a/app/actions/authorizeActions.jsx b/app/actions/authorizeActions.jsx new file mode 100644 index 0000000..52d2234 --- /dev/null +++ b/app/actions/authorizeActions.jsx @@ -0,0 +1,138 @@ +// - Import react components +import {firebaseRef, firebaseAuth} from 'app/firebase/' +import moment from 'moment' +import {push} from 'react-router-redux' + +// - Import action types +import * as types from 'actionTypes' + +// - Import actions +import * as globalActions from 'globalActions' + +/* _____________ CRUD DB _____________ */ + +/** + * Log in user in server + * @param {string} email + * @param {string} password + */ +export var dbLogin = (email, password) => { + return (dispatch, getState) => { + dispatch(globalActions.showNotificationRequest()) + + return firebaseAuth().signInWithEmailAndPassword(email, password).then((result) => { + dispatch(globalActions.showNotificationSuccess()) + dispatch(login(result.uid)) + dispatch(push('/')) + }, (error) => dispatch(globalActions.showErrorMessage(error.code))) + } +} + +/** + * Log out user in server + */ +export var dbLogout = () => { + return (dispatch, getState) => { + return firebaseAuth().signOut().then((result) => { + dispatch(logout()) + dispatch(push('/login')) + + }, (error) => dispatch(globalActions.showErrorMessage(error.code))) + } + +} + +/** + * Register user in database + * @param {object} user + */ +export var dbSignup = (user) => { + return (dispatch, getState) => { + dispatch(globalActions.showNotificationRequest()) + return firebaseAuth().createUserWithEmailAndPassword(user.email, user.password).then((signupResult) => { + firebaseRef.child(`users/${signupResult.uid}/info`).set({ + ...user + }).then((result) => { + + dispatch(globalActions.showNotificationSuccess()) + + }, (error) => dispatch(globalActions.showErrorMessage(error.code))) + + dispatch(signup({ + uid: signupResult.uid, + ...user + })) + dispatch(push('/')) + }, (error) => dispatch(globalActions.showErrorMessage(error.code))) + } + +} + +/** + * Change user's password + * @param {string} newPassword + */ +export const dbUpdatePassword = (newPassword) => { + return (dispatch, getState) => { + dispatch(globalActions.showNotificationRequest()) + firebaseAuth().onAuthStateChanged((user) => { + if (user) { + + user.updatePassword(newPassword).then(() => { + // Update successful. + dispatch(globalActions.showNotificationSuccess()) + dispatch(updatePassword()) + dispatch(push('/')) + }, (error) => { + // 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} uid + */ +export var login = (uid) => { + return {type: types.LOGIN, authed: true, uid} +} + +/** + * Logout user + */ +export var logout = () => { + return {type: types.LOGOUT} +} + +/** + * Register user + * @param {object} user + */ +export var signup = (user) => { + return { + type: types.SIGNUP, + ...user + } + +} + +/** + * Update user's password + */ +export const updatePassword = () => { + return {type: types.UPDATE_PASSWORD} +} + diff --git a/app/actions/circleActions.jsx b/app/actions/circleActions.jsx new file mode 100644 index 0000000..59cc8f0 --- /dev/null +++ b/app/actions/circleActions.jsx @@ -0,0 +1,337 @@ +// - Import firebase component +import firebase, { firebaseRef } from 'app/firebase/' + +// - Import utility components +import moment from 'moment' + +// - Import action types +import * as types from 'actionTypes' + + +// - Import actions +import * as globalActions from 'globalActions' +import * as postActions from 'postActions' +import * as userActions from 'userActions' +import * as notifyActions from 'notifyActions' + + + + + +/* _____________ CRUD DB _____________ */ + + +/** + * Add a circle + * @param {string} circleName + */ +export var dbAddCircle = (circleName) => { + return (dispatch, getState) => { + + let uid = getState().authorize.uid + let circle = { + creationDate: moment().unix(), + name: circleName, + users: {} + } + + let circleRef = firebaseRef.child(`userCircles/${uid}/circles`).push(circle) + return circleRef.then(() => { + dispatch(addCircle(uid, { + ...circle, + id: circleRef.key + })) + + }, (error) => dispatch(globalActions.showErrorMessage(error.message))) + + } +} + +/** + * Add a user in a circle + * @param {string} cid is circle identifier + * @param {object} userFollowing is the user for following + */ +export var dbAddFollowingUser = (cid, userFollowing) => { + return (dispatch, getState) => { + + let uid = getState().authorize.uid + let user = getState().user.info[uid] + + let userCircle = { + creationDate: moment().unix(), + fullName: userFollowing.fullName, + avatar: userFollowing.avatar || '' + } + let userFollower = { + creationDate: moment().unix(), + fullName: user.fullName, + avatar: user.avatar || '', + approved: false + } + let updates = {} + updates[`userCircles/${uid}/circles/${cid}/users/${userFollowing.userId}`] = userCircle + updates[`userCircles/${userFollowing.userId}/circles/-Followers/users/${uid}`] = userFollower + return firebaseRef.update(updates).then((result) => { + dispatch(addFollowingUser(uid, cid, userFollowing.userId, { ...userCircle })) + + dispatch(notifyActions.dbAddNotify( + { + description: `${user.fullName} follow you.`, + url: `/${uid}`, + notifyRecieverUserId: userFollowing.userId, notifierUserId: uid + })) + + }, (error) => { + dispatch(globalActions.showErrorMessage(error.message)) + }) + } +} + + +/** + * Add a user in a circle + * @param {string} cid is circle identifier + * @param {string} followingId following user identifier + */ +export var dbDeleteFollowingUser = (cid, followingId) => { + return (dispatch, getState) => { + + let uid = getState().authorize.uid + + let updates = {} + updates[`userCircles/${uid}/circles/${cid}/users/${followingId}`] = null + updates[`userCircles/${followingId}/circles/-Followers/users/${uid}`] = null + return firebaseRef.update(updates).then((result) => { + dispatch(deleteFollowingUser(uid, cid, followingId)) + }, (error) => { + dispatch(globalActions.showErrorMessage(error.message)) + }) + } +} + +/** + * Update a circle from database + * @param {object} newCircle + */ +export const dbUpdateCircle = (newCircle) => { + return (dispatch, getState) => { + + // Get current user id + var uid = getState().authorize.uid + + // Write the new data simultaneously in the list + let updates = {} + let circle = getState().circle.userCircles[uid][newCircle.id] + let updatedCircle = { + name: newCircle.name || circle.name, + users: newCircle.users ? newCircle.users : (circle.users || []) + } + updates[`userCircles/${uid}/circles/${newCircle.id}`] = updatedCircle + return firebaseRef.update(updates).then((result) => { + dispatch(updateCircle(uid, { id: newCircle.id, ...updatedCircle })) + }, (error) => { + dispatch(globalActions.showErrorMessage(error.message)) + }) + } + +} + + +/** + * Delete a circle from database + * @param {string} id is circle identifier + */ +export const dbDeleteCircle = (id) => { + return (dispatch, getState) => { + + // Get current user id + var uid = getState().authorize.uid + + // Write the new data simultaneously in the list + var updates = {}; + updates[`userCircles/${uid}/circles/${id}`] = null; + + return firebaseRef.update(updates).then((result) => { + dispatch(deleteCircle(uid, id)) + }, (error) => { + dispatch(globalActions.showErrorMessage(error.message)) + }); + } + +} + +/** + * Get all user circles from data base + */ +export const dbGetCircles = () => { + return (dispatch, getState) => { + var uid = getState().authorize.uid + if (uid) { + var circlesRef = firebaseRef.child(`userCircles/${uid}/circles`); + + return circlesRef.once('value').then((snapshot) => { + var circles = snapshot.val() || {}; + var parsedCircles = {}; + 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)) + }) + } + parsedCircles[circleId] = { + id: circleId, + ...circles[circleId] + } + }) + + dispatch(addCircles(uid, parsedCircles)); + }); + + } + } +} + + +/** + * Get all user circles from data base by user id + */ +export const dbGetCirclesByUserId = (uid) => { + return (dispatch, getState) => { + + if (uid) { + var circlesRef = firebaseRef.child(`userCircles/${uid}/circles`); + + return circlesRef.once('value').then((snapshot) => { + var circles = snapshot.val() || {}; + var parsedCircles = {}; + Object.keys(circles).forEach((circleId) => { + parsedCircles[circleId] = { + id: circleId, + ...circles[circleId] + } + }) + dispatch(addCircles(uid, parsedCircles)); + }) + + } + } +} + + + + + + +/* _____________ CRUD State _____________ */ + +/** + * Add a normal circle + * @param {string} uid is user identifier + * @param {object} circle + */ +export const addCircle = (uid, circle) => { + return { + type: types.ADD_CIRCLE, + payload: { uid, circle } + } +} + +/** + * Update a circle + * @param {string} uid is user identifier + * @param {object} circle + */ +export const updateCircle = (uid, circle) => { + return { + type: types.UPDATE_CIRCLE, + payload: { uid, circle } + } +} + +/** + * Delete a circle + * @param {string} uid is user identifier + * @param {string} id is circle identifier + */ +export const deleteCircle = (uid, id) => { + return { + type: types.DELETE_CIRCLE, + payload: { uid, id } + } +} + + +/** + * Add a list of circle + * @param {string} uid + * @param {[object]} circles + */ +export const addCircles = (uid, circles) => { + return { + type: types.ADD_LIST_CIRCLE, + payload: { uid, circles } + } +} + +/** + * Clea all data in circle store + */ +export const clearAllCircles = () => { + return { + type: types.CLEAR_ALL_CIRCLES + } +} + + +/** + * Open circle settings + */ +export const openCircleSettings = (uid, id) => { + return { + type: types.OPEN_CIRCLE_SETTINGS, + payload: { uid, id } + } + +} + +/** + * Close open circle settings + */ +export const closeCircleSettings = (uid, id) => { + return { + type: types.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 {object} userCircle information about following user + */ +export const addFollowingUser = (uid, cid, followingId, userCircle) => { + return { + type: types.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, cid, followingId) => { + return { + type: types.DELETE_FOLLOWING_USER, + payload: { uid, cid, followingId } + } + +} diff --git a/app/actions/commentActions.jsx b/app/actions/commentActions.jsx new file mode 100644 index 0000000..80a6648 --- /dev/null +++ b/app/actions/commentActions.jsx @@ -0,0 +1,231 @@ +// - Import react components +import moment from 'moment' +import { firebaseRef } from 'app/firebase/' + +// - Import action types +import * as types from 'actionTypes' + +// - Import actions +import * as globalActions from 'globalActions' +import * as notifyActions from 'notifyActions' + + + +/* _____________ CRUD DB _____________ */ + +/** + * Add comment to database + * @param {object} newComment user comment + * @param {function} callBack will be fired when server responsed + */ +export const dbAddComment = (newComment, callBack) => { + return (dispatch, getState) => { + + dispatch(globalActions.showTopLoading()) + + + var uid = getState().authorize.uid + var comment = { + postId: newComment.postId, + score: 0, + text: newComment.text, + creationDate: moment().unix(), + userDisplayName: getState().user.info[uid].fullName, + userAvatar: getState().user.info[uid].avatar, + userId: uid + } + + var commentRef = firebaseRef.child(`postComments/${newComment.postId}`).push(comment) + return commentRef.then(() => { + dispatch(addComment( + { + comment, + postId: newComment.postId, + id: commentRef.key, + editorStatus: false + })) + callBack() + dispatch(globalActions.hideTopLoading()) + + if (newComment.ownerPostUserId !== uid) + dispatch(notifyActions.dbAddNotify( + { + description: 'Add comment on your post.', + url: `/${newComment.ownerPostUserId}/posts/${newComment.postId}`, + notifyRecieverUserId: newComment.ownerPostUserId, notifierUserId: uid + })) + + }, (error) => { + dispatch(globalActions.showErrorMessage(error.message)) + dispatch(globalActions.hideTopLoading()) + + }) + + } +} + +/** + * Get all comments from database + */ +export const dbGetComments = () => { + return (dispatch, getState) => { + var uid = getState().authorize.uid + if (uid) { + var commentsRef = firebaseRef.child(`postComments`); + + return commentsRef.on('value', (snapshot) => { + var comments = snapshot.val() || {}; + 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 + */ +export const dbUpdateComment = (id, postId, text) => { + return (dispatch, getState) => { + + dispatch(globalActions.showTopLoading()) + + + // Get current user id + var uid = getState().authorize.uid + + // Write the new data simultaneously in the list + var updates = {}; + let comment = getState().comment.postComments[postId][id] + updates[`postComments/${postId}/${id}`] = { + postId: postId, + score: comment.score, + text: text, + creationDate: comment.creationDate, + userDisplayName: comment.userDisplayName, + userAvatar: comment.userAvatar, + userId: uid + } + return firebaseRef.update(updates).then((result) => { + dispatch(updateComment({ id, postId, text, editorStatus: false })) + dispatch(globalActions.hideTopLoading()) + + }, (error) => { + 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, postId) => { + return (dispatch, getState) => { + + dispatch(globalActions.showTopLoading()) + + + // Get current user id + var uid = getState().authorize.uid + + // Write the new data simultaneously in the list + var updates = {}; + updates[`postComments/${postId}/${id}`] = null; + + return firebaseRef.update(updates).then((result) => { + dispatch(deleteComment(id, postId)) + dispatch(globalActions.hideTopLoading()) + + }, (error) => { + dispatch(globalActions.showErrorMessage(error.message)) + dispatch(globalActions.hideTopLoading()) + + }) + } + +} + +/* _____________ CRUD State _____________ */ + + +/** + * Add comment + * @param {object} data + */ +export const addComment = (data) => { + + return { + type: types.ADD_COMMENT, + payload: data + } +} + +/** + * Update comment + * @param {object} data + */ +export const updateComment = (data) => { + + return { + type: types.UPDATE_COMMENT, + payload: data + } +} + +/** + * Add comment list + * @param {[object]} postComments an array of comments + */ +export const addCommentList = (postComments) => { + + return { + type: types.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, postId) => { + return { type: types.DELETE_COMMENT, payload: { id, postId } } + +} + +/** + * Clear all data + */ +export const clearAllData = () => { + return { + type: types.CLEAR_ALL_DATA_COMMENT + } +} + +export const openCommentEditor = (comment) => { + + return { + type: types.OPEN_COMMENT_EDITOR, + payload: comment + } +} + +export const closeCommentEditor = (comment) => { + + return { + type: types.CLOSE_COMMENT_EDITOR, + payload: comment + } +} + diff --git a/app/actions/fileActions.jsx b/app/actions/fileActions.jsx new file mode 100644 index 0000000..80dd4b5 --- /dev/null +++ b/app/actions/fileActions.jsx @@ -0,0 +1,52 @@ +// - Import react components +import moment from 'moment' + +// - Import image gallery action types +import * as types from 'actionTypes' + +// - Import actions +import * as imageGalleryActions from 'imageGalleryActions' + +// - Import firebase +import {storageRef,firebaseRef} from 'app/firebase/' + + +// - Upload file start +export const uploadFile = (file) => { + return (dispatch,getState) => { + + + + + } + +} + + +// - Upload file error +export const uploadError = (error) => { + return{ + type: types.UPLOAD_FILE_ERROR, + error + } +} + +// - Uplaod file complete +export const uploadComplete = (result) => { + return{ + type: types.UPLOAD_FILE_COMPLETE, + result + } + +} + + + + +// - Download file +export const downloadFile = (fileName) => { + return { + type: types.DOWNLOAD_FILE, + fileName + } +} diff --git a/app/actions/globalActions.jsx b/app/actions/globalActions.jsx new file mode 100644 index 0000000..154e213 --- /dev/null +++ b/app/actions/globalActions.jsx @@ -0,0 +1,218 @@ +// - Import image gallery action types +import * as types from 'actionTypes' + +// - Import actions +import * as postActions from 'postActions' +import * as commentActions from 'commentActions' +import * as userActions from 'userActions' + +/** + * Progress change + * @param {string} percent + * @param {boolean} visible + */ +export const progressChange = (percent, visible) => { + return { + type: types.PROGRESS_CHANGE, + payload: {percent, visible} + } + +} + +/** + * Default data loaded status will be true + * @param {boolean} status + */ +export const defaultDataEnable = (status) => { + return{ + type: types.DEFAULT_DATA_ENABLE + } +} + +/** + * Default data loaded status will be false + * @param {boolean} status + */ +export const defaultDataDisable = (status) => { + return{ + type: types.DEFAULT_DATA_DISABLE + } +} + + + +/** + * Show notification normally + * @param {string} message + */ +export const showNotificationNormal = (message) => { + return (dispatch,getState) => { + dispatch(showNormalMessage(message)) + + } +} + + +// - Show global normal message +export const showNormalMessage = (message) => { + return{ + type: types.SHOW_NORMAL_MESSAGE_GLOBAL, + message + } + +} + +// - Show notification of request +export const showNotificationRequest = () => { + return (dispatch,getState) => { + dispatch(showSendRequestMessage()) + } +} + + +// - Show global request sent message +export const showSendRequestMessage = () => { + return{ + type: types.SHOW_SEND_REQUEST_MESSAGE_GLOBAL + } + +} + +// - Show notification of success +export const showNotificationSuccess = () => { + return (dispatch,getState) => { + dispatch(showRequestSuccessMessage()) + + } +} + +// - Show global request successful message +export const showRequestSuccessMessage = () => { + return{ + type: types.SHOW_REQUEST_SUCCESS_MESSAGE_GLOBAL + } +} + +/** + * Hide global message + */ +export const hideMessage = () => { + return{ + type: types.HIDE_MESSAGE_GLOBAL + } + +} + +/** + * Show error message + * @param {string} message + */ +export const showErrorMessage = (message) => { + return { + type: types.SHOW_ERROR_MESSAGE_GLOBAL, + payload: message + } + +} + +/** + * Set header title + */ +export const setHeaderTitleOpt = (opt,payload) => { + return (dispatch,getState) => { + switch (opt) { + 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) => { + return{ + type: types.SET_HEADER_TITLE, + payload: text + } + +} + +/** + * Open post write + */ +export const openPostWrite = () => { + return{ + type: types.OPEN_POST_WRITE + } + +} + +/** + * Close post write + */ +export const closePostWrite = () => { + return{ + type: types.CLOSE_POST_WRITE + } + +} + +/** + * Show top loading + */ +export const showTopLoading = () => { + return{ + type: types.SHOW_TOP_LOADING + } + +} + +/** + * Hide top loading + */ +export const hideTopLoading = () => { + return{ + type: types.HIDE_TOP_LOADING + } + +} + + + + +/** + * Store temp data + */ +export const temp = (data) => { + return{ + type: types.TEMP, + payload: data + } + +} + + + +// - Load data for guest +export const loadDataGuest = () => { + return (dispatch,getState) => { + var userString = "{\"avatar\":\"http://www.freeimageslive.com/galleries/nature/abstract/preview/frostyleaves00406.jpg\",\"contact\":\"amir.gholzam@live.com\",\"email\":\"amir.gholzam@live.com\",\"fullName\":\"React Social Blog\",\"password\":\"123qwe\",\"summary\":\" The React Social Blog (RSB) Application is a diary app blog\"}" + var postString = '[{"id":"-KkauHBOZXlALsHIrNsvsq","body":"The React Social Blog (RSB) Application is a diary app blog based on Semantic ui React for UI, Redux with react-redux for managing states and React for managing DOM .It is an open source project as a portfolio.\\n\\nI will be really grateful to receive any issue: \\nhttps://github.com/Qolzam/react-blog/issues\\n\\n \\n","commentCounter":0,"creationDate":1495301432,"deletationDate":"","deleted":false,"image":"http://www.freeimageslive.com/galleries/nature/abstract/preview/frosty_grass.jpg","lastEditDate":"","ownerAvatar":"http://www.freeimageslive.com/galleries/nature/abstract/preview/frostyleaves00406.jpg","ownerDisplayName":"React Social Blog","ownerUserId":"5flWuB1RieZR7GIAwHYMPYaI5o33","postTypeId":1,"score":0,"video":"","viewCount":0},{"id":"-KkauHBOZXlALsHIrNIq","body":"It is a demo website","commentCounter":0,"creationDate":1495301432,"deletationDate":"","deleted":false,"image":"http://www.freeimageslive.com/galleries/nature/environment/pics/eaten%20_flower0905.jpg","lastEditDate":"","ownerAvatar":"http://www.freeimageslive.com/galleries/nature/abstract/preview/frostyleaves00406.jpg","ownerDisplayName":"React Social Blog","ownerUserId":"5flWuB1RieZR7GIAwHYMPYaI5o33","postTypeId":1,"score":0,"video":"","viewCount":0},{"id":"-KkauHBOZXlsLsHIrNIq","body":"This is an open source project","commentCounter":0,"creationDate":1495301432,"deletationDate":"","deleted":false,"image":"http://www.freeimageslive.com/galleries/nature/environment/pics/eaten%20_flower0905.jpg","lastEditDate":"","ownerAvatar":"http://www.freeimageslive.com/galleries/nature/abstract/preview/frostyleaves00406.jpg","ownerDisplayName":"React Social Blog","ownerUserId":"5flWuB1RieZR7GIAwHYMPYaI5o33","postTypeId":1,"score":0,"video":"","viewCount":0},{"id":"-KkaurBOZXlALsHIrNIq","body":"I have documentaion, testing, add some features DEBUG in my todo list","commentCounter":0,"creationDate":1495301432,"deletationDate":"","deleted":false,"image":"http://www.freeimageslive.com/galleries/nature/environment/pics/eaten%20_flower0905.jpg","lastEditDate":"","ownerAvatar":"http://www.freeimageslive.com/galleries/nature/abstract/preview/frostyleaves00406.jpg","ownerDisplayName":"React Social Blog","ownerUserId":"5flWuB1RieZR7GIAwHYMPYaI5o33","postTypeId":1,"score":0,"video":"","viewCount":0}]' + var postCommentString = "{\"postComments\":{\"-KkauHBOZXlALsHIrNIq\":{\"-KkaxkH1WmfcQaidsNHK3R\":{\"creationDate\":1495302341,\"postId\":\"-KkauHBOZXlALsHIrNIq\",\"score\":0,\"text\":\"On developing :)\",\"userAvatar\":\"http://www.freeimageslive.com/galleries/nature/abstract/preview/frostyleaves00406.jpg\",\"userDisplayName\":\"React Social Blog\",\"userId\":\"5flWuB1RieZR7GIAwHYMPYaI5o33\"},\"-KkafsdfxkH1WmfcQaiNHK3R\":{\"creationDate\":1495302341,\"postId\":\"-KkauHBOZXlALsHIrNIq\",\"score\":0,\"text\":\"This is a good project for lorem if you want to learn more and deeply about react and ui frameworkes. I'm preparing a good document for lorem :)\",\"userAvatar\":\"http://www.freeimageslive.com/galleries/nature/abstract/preview/frostyleaves00406.jpg\",\"userDisplayName\":\"React Social Blog\",\"userId\":\"5flWuB1RieZR7GIAwHYMPYaI5o33\"},\"-KkaxkH1WfrmfcQaiNHK3R\":{\"creationDate\":1495302341,\"postId\":\"-KkauHBOZXlALsHIrNIq\",\"score\":0,\"text\":\"On I'm so happy now that I have you react-blog so far I was looking for you oh it's just lorem so don't make it serious :)\",\"userAvatar\":\"http://www.freeimageslive.com/galleries/nature/abstract/preview/frostyleaves00406.jpg\",\"userDisplayName\":\"React Social Blog\",\"userId\":\"5flWuB1RieZR7GIAwHYMPYaI5o33\"},\"-KkaxkH1WmfcQakeiNHK3R\":{\"creationDate\":1495302341,\"postId\":\"-KkauHBOZXlALsHIrNIq\",\"score\":0,\"text\":\"On developing :)\",\"userAvatar\":\"http://www.freeimageslive.com/galleries/nature/abstract/preview/frostyleaves00406.jpg\",\"userDisplayName\":\"React Social Blog\",\"userId\":\"5flWuB1RieZR7GIAwHYMPYaI5o33\"},\"-KkaxkH1WmfcQpraiNHK3R\":{\"creationDate\":1495302341,\"postId\":\"-KkauHBOZXlALsHIrNIq\",\"score\":0,\"text\":\"On developing :)\",\"userAvatar\":\"http://www.freeimageslive.com/galleries/nature/abstract/preview/frostyleaves00406.jpg\",\"userDisplayName\":\"React Social Blog\",\"userId\":\"5flWuB1RieZR7GIAwHYMPYaI5o33\"}}}}" + var user = JSON.parse(userString) + dispatch(userActions.addUserInfo(user)) + var post = JSON.parse(postString) + dispatch(postActions.addPosts(post)) + var postComment = JSON.parse(postCommentString) + dispatch(commentActions.addCommentList(postComment.postComments)) +} + +} diff --git a/app/actions/imageGalleryActions.jsx b/app/actions/imageGalleryActions.jsx new file mode 100644 index 0000000..882ecda --- /dev/null +++ b/app/actions/imageGalleryActions.jsx @@ -0,0 +1,248 @@ +// - Import react componetns +import moment from 'moment' +import { firebaseRef, firebaseAuth, storageRef } from 'app/firebase/' + + +// - Import action types +import * as types from 'actionTypes' + +// - Import actions +import * as globalActions from 'globalActions' + +// - Import app API +import FileAPI from 'FileAPI' + +/* _____________ UI Actions _____________ */ + + +/** + * Clear selected data + */ +export const clearSelectData = () => { + return { type: types.CLEARS_SELECT_IMAGE_GALLERY } + +} + +/** + * Clear all data in image gallery store + */ +export const clearAllData = () => { + return { + type: types.CLEAT_ALL_DATA_IMAGE_GALLERY + } +} + + +/** + * Download images for image gallery + */ +export const downloadForImageGallery = () => { + return (dispatch, getState) => { + var uid = getState().authorize.uid + if (uid) { + var imagesRef = firebaseRef.child(`userFiles/${uid}/files/images`); + + return imagesRef.once('value').then((snapshot) => { + var images = snapshot.val() || {}; + var parsedImages = [] + Object.keys(images).forEach((imageId) => { + parsedImages.push({ + id: imageId, + ...images[imageId] + }) + }) + dispatch(addImageList(parsedImages)); + }) + + } + } + +} + +/* _____________ CRUD Database_____________ */ + +/** + * Save image in the server + * @param {string} imageName is the name of image + */ +export const dbSaveImage = (imageName) => { + return (dispatch, getState) => { + + var uid = getState().authorize.uid + var image = { + creationDate: moment().unix(), + deletationDate: '', + name: imageName, + ownerUserId: uid, + lastEditDate: '', + deleted: false + } + + var imageRef = firebaseRef.child(`userFiles/${uid}/files/images`).push(image) + return imageRef.then(() => { + dispatch(addImage({ + ...image, + id: imageRef.key + })) + }) + + } +} + +/** + * Delete an image from database + * @param {string} id of image + */ +export const dbDeleteImage = (id) => { + return (dispatch, getState) => { + + // Get current user id + var uid = getState().authorize.uid + + // Write the new data simultaneously in the list + var updates = {}; + updates[`userFiles/${uid}/files/images/${id}`] = null; + + return firebaseRef.update(updates).then((result) => { + dispatch(deleteImage(id)) + console.log('image removed: ', id); + }, (error) => { + console.log(error); + }); + } + +} + +/** + * Upload image on the server + * @param {file} file + * @param {string} fileName + */ +export const dbUploadImage = (file, fileName) => { + return (dispatch, getState) => { + // Create a storage refrence + var storegeFile = storageRef.child(`images/${fileName}`) + + // Upload file + var task = storegeFile.put(file) + dispatch(globalActions.showTopLoading()) + + // Upload storage bar + task.on('state_changed', (snapshot) => { + var percentage = (snapshot.bytesTransferred / snapshot.totalBytes) * 100 + dispatch(globalActions.progressChange(percentage, true)) + + + }, (error) => { + dispatch(globalActions.showErrorMessage(error.code)) + dispatch(globalActions.hideTopLoading()) + + }, (complete) => { + dispatch(globalActions.progressChange(100, false)) + dispatch(dbSaveImage(fileName)) + dispatch(globalActions.hideTopLoading()) + + }) + } +} + +/** + * Download image from server + * @param {string} fileName + */ +export const dbDownloadImage = (fileName) => { + return (dispatch, getState) => { + + if (getState().imageGallery.imageURLList[fileName] && fileName !== '') + return + + if(getState().imageGallery.imageRequests.indexOf(fileName) > -1) + return + dispatch(sendImageRequest(fileName)) + + // Create a reference to the file we want to download + var starsRef = storageRef.child(`images/${fileName}`); + + // Get the download URL + starsRef.getDownloadURL().then((url) => { + // Insert url into an tag to "download" + if (!getState().imageGallery.imageURLList[fileName] || fileName === '') + dispatch(setImageURL(fileName, url)) + }).catch((error) => { + + // A full list of error codes is available at + // https://firebase.google.com/docs/storage/web/handle-errors + switch (error.code) { + case 'storage/object_not_found': + // File doesn't exist + dispatch(globalActions.showErrorMessage('storage/object_not_found')) + break; + + case 'storage/unauthorized': + // User doesn't have permission to access the object + dispatch(globalActions.showErrorMessage('storage/unauthorized')) + break; + + case 'storage/canceled': + // User canceled the upload + dispatch(globalActions.showErrorMessage('storage/canceled')) + break; + + case 'storage/unknown': + // Unknown error occurred, inspect the server response + dispatch(globalActions.showErrorMessage('storage/unknown')) + break; + } + }); + } +} + +/* _____________ CRUD State _____________ */ + +/** + * Add image list to image gallery + * @param {[object]} images is an array of images + */ +export const addImageList = (images) => { + return { type: types.ADD_IMAGE_LIST_GALLERY, images } +} + +/** + * Add image to image gallery + * @param {object} image + */ +export const addImage = (image) => { + return { type: types.ADD_IMAGE_GALLERY, image } +} + +/** + * Delete an image + * @param {string} id is an image identifier + */ +export const deleteImage = (id) => { + return { type: types.DELETE_IMAGE, id } + +} + +/** + * Delete an image + */ +export const setImageURL = (name, url) => { + return { + type: types.SET_IMAGE_URL, + payload: { name, url } + } + +} + +/** + * Send image request + */ +export const sendImageRequest = (name) => { + return { + type: types.SEND_IMAGE_REQUEST, + payload: name + } + + +} \ No newline at end of file diff --git a/app/actions/imageUploaderActions.jsx b/app/actions/imageUploaderActions.jsx new file mode 100644 index 0000000..fddcf50 --- /dev/null +++ b/app/actions/imageUploaderActions.jsx @@ -0,0 +1,20 @@ +// - Import image uploader action types +import * as types from 'actionTypes' + + +// - Image uploader actions + +export const openImageUploader = (status)=> { + return { + type: types.OPEN_IMAGE_UPLOADER, + status + } +} + +export const openImageEditor = (editStatus) =>{ + return{ + type: types.OPEN_IMAGE_EDITOR, + editStatus + + } +} diff --git a/app/actions/notifyActions.jsx b/app/actions/notifyActions.jsx new file mode 100644 index 0000000..811a2de --- /dev/null +++ b/app/actions/notifyActions.jsx @@ -0,0 +1,168 @@ +// - Import react components +import moment from 'moment' +import { firebaseRef } from 'app/firebase/' + +// - Import action types +import * as types from 'actionTypes' + +// - Import actions +import * as globalActions from 'globalActions' +import * as userActions from 'userActions' + + +/* _____________ CRUD DB _____________ */ + +/** + * Add notificaition to database + * @param {object} newNotify user notificaition + */ +export const dbAddNotify = (newNotify) => { + return (dispatch, getState) => { + + var uid = getState().authorize.uid + var notify = { + description: newNotify.description, + url: newNotify.url, + notifierUserId: newNotify.notifierUserId, + isSeen: false + } + + var notifyRef = firebaseRef.child(`userNotifies/${newNotify.notifyRecieverUserId}`).push(notify) + return notifyRef.then(() => { + dispatch(addNotify()) + }, (error) => dispatch(globalActions.showErrorMessage(error.message))) + + } +} + +/** + * Get all notificaitions from database + */ +export const dbGetNotifies = () => { + return (dispatch, getState) => { + var uid = getState().authorize.uid + if (uid) { + var notifiesRef = firebaseRef.child(`userNotifies/${uid}`); + + return notifiesRef.on('value', (snapshot) => { + var notifies = snapshot.val() || {}; + + Object.keys(notifies).forEach((key => { + if (!getState().user.info[notifies[key].notifierUserId]) { + dispatch(userActions.dbGetUserInfoByUserId(notifies[key].notifierUserId)) + + } + })) + dispatch(addNotifyList(notifies)) + }) + + } + } +} + + +/** + * Delete a notificaition from database + * @param {string} id of notificaition + */ +export const dbDeleteNotify = (id) => { + return (dispatch, getState) => { + + // Get current user id + var uid = getState().authorize.uid + + // Write the new data simultaneously in the list + var updates = {}; + updates[`userNotifies/${uid}/${id}`] = null; + + return firebaseRef.update(updates).then((result) => { + dispatch(deleteNotify(id)) + }, (error) => dispatch(globalActions.showErrorMessage(error.message))) + } + +} + + + +/** + * Make seen a notificaition from database + * @param {string} id of notificaition + */ +export const dbSeenNotify = (id) => { + return (dispatch, getState) => { + + // Get current user id + var uid = getState().authorize.uid + let notify = getState().notify.userNotifies[id] + let updatedNotify = { + description: notify.description, + url: notify.url, + notifierUserId: notify.notifierUserId, + isSeen: true + } + // Write the new data simultaneously in the list + var updates = {}; + updates[`userNotifies/${uid}/${id}`] = updatedNotify; + + return firebaseRef.update(updates).then((result) => { + dispatch(seenNotify(id)) + }, (error) => dispatch(globalActions.showErrorMessage(error.message))) + } + +} + +/* _____________ CRUD State _____________ */ + + +/** + * Add notificaition + * @param {object} data + */ +export const addNotify = () => { + + return { + type: types.ADD_NOTIFY + } +} + +/** + * Add notificaition list + * @param {[object]} userNotifies an array of notificaitions + */ +export const addNotifyList = (userNotifies) => { + + return { + type: types.ADD_NOTIFY_LIST, + payload: userNotifies + } +} + + +/** + * Delete a notificaition + * @param {string} id of notificaition + */ +export const deleteNotify = (id) => { + return { type: types.DELETE_NOTIFY, payload: id } + +} + +/** + * Change notificaition to has seen status + * @param {string} id of notificaition + */ +export const seenNotify = (id) => { + return { type: types.SEEN_NOTIFY, payload: id } + +} + +/** + * Clear all data + */ +export const clearAllNotifications = () => { + return { + type: types.CLEAR_ALL_DATA_NOTIFY + } +} + + diff --git a/app/actions/postActions.jsx b/app/actions/postActions.jsx new file mode 100644 index 0000000..1291268 --- /dev/null +++ b/app/actions/postActions.jsx @@ -0,0 +1,339 @@ +// - Import firebase component +import firebase, {firebaseRef} from 'app/firebase/' + +// - Import utility components +import moment from 'moment' + +// - Import action types +import * as types from 'actionTypes' + +// - Import actions +import * as globalActions from 'globalActions' + + + + + + + +/* _____________ CRUD DB _____________ */ + +/** + * Add a normal post + * @param {object} newPost + * @param {function} callBack + */ +export var dbAddPost = (newPost,callBack) => { + return(dispatch,getState) => { + + var uid = getState().authorize.uid + var post = { + postTypeId: 0, + creationDate: moment().unix(), + deletationDate: '', + score: 0, + viewCount: 0, + body: newPost.body, + ownerUserId: uid, + ownerDisplayName: newPost.name, + ownerAvatar: newPost.avatar, + lastEditDate: '', + tags: newPost.tags || [], + commentCounter: 0, + image:'', + video:'', + disableComments: newPost.disableComments, + disableSharing: newPost.disableSharing, + deleted:false + } + + + var postRef = firebaseRef.child(`userPosts/${uid}/posts`).push(post) + return postRef.then(()=>{ + dispatch(addPost(uid,{ + ...post, + id: postRef.key + })) + callBack() + },(error) => dispatch(globalActions.showErrorMessage(error.message))) + } +} + + +/** + * Add a post with image + * @param {object} newPost + * @param {function} callBack + */ + export const dbAddImagePost = (newPost,callBack) => { + return(dispatch,getState) => { + + dispatch(globalActions.showTopLoading()) + + var uid = getState().authorize.uid + var post = { + postTypeId: 1, + creationDate: moment().unix(), + deletationDate: '', + score: 0, + viewCount: 0, + body: newPost.body, + ownerUserId: uid, + ownerDisplayName: newPost.name, + ownerAvatar: newPost.avatar, + lastEditDate: '', + tags: newPost.tags || [], + commentCounter: 0, + image: newPost.image || '', + video:'', + disableComments: newPost.disableComments ? newPost.disableComments : false, + disableSharing: newPost.disableSharing ? newPost.disableSharing : false, + deleted:false + } + + + var postRef = firebaseRef.child(`userPosts/${uid}/posts`).push(post) + return postRef.then(()=>{ + dispatch(addPost(uid,{ + ...post, + id: postRef.key + })) + callBack() + dispatch(globalActions.hideTopLoading()) + + }) + } + + } + +/** + * 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,callBack) => { + console.log(newPost) + return (dispatch, getState) => { + + dispatch(globalActions.showTopLoading()) + + // Get current user id + var uid = getState().authorize.uid + + // Write the new data simultaneously in the list + let updates = {}; + let post = getState().post.userPosts[uid][newPost.id] + let updatedPost = { + postTypeId: post.postTypeId, + creationDate: post.creationDate, + deletationDate: '', + 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, + 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 + } + updates[`userPosts/${uid}/posts/${newPost.id}`] = updatedPost + return firebaseRef.update(updates).then((result) => { + + dispatch(updatePost(uid,{id:newPost.id, ...updatedPost})) + callBack() + dispatch(globalActions.hideTopLoading()) + + }, (error) => { + dispatch(globalActions.showErrorMessage(error.message)) + dispatch(globalActions.hideTopLoading()) + + }) + } + +} + +/** + * Delete a post from database + * @param {string} id is post identifier + */ +export const dbDeletePost = (id) => { + return (dispatch, getState) => { + + dispatch(globalActions.showTopLoading()) + + // Get current user id + var uid = getState().authorize.uid + + // Write the new data simultaneously in the list + var updates = {}; + updates[`userPosts/${uid}/posts/${id}`] = null; + + return firebaseRef.update(updates).then((result) => { + dispatch(deletePost(uid,id)) + dispatch(globalActions.hideTopLoading()) + + }, (error) => { + dispatch(globalActions.showErrorMessage(error.message)) + dispatch(globalActions.hideTopLoading()) + }); + } + +} + + /** + * Get all user posts from data base + */ + export const dbGetPosts = () => { + return (dispatch, getState) => { + var uid = getState().authorize.uid + if (uid) { + var postsRef = firebaseRef.child(`userPosts/${uid}/posts`); + + return postsRef.once('value').then((snapshot) => { + var posts = snapshot.val() || {}; + var parsedPosts = {}; + Object.keys(posts).forEach((postId) => { + parsedPosts[postId]={ + id: postId, + ...posts[postId] + }; + }); + + dispatch(addPosts(uid,parsedPosts)); + }); + + } + } + } + + /** + * Get all user posts from data base + */ + export const dbGetPostById = (uid,postId) => { + return (dispatch, getState) => { + if (uid) { + var postsRef = firebaseRef.child(`userPosts/${uid}/posts/${postId}`); + + return postsRef.once('value').then((snapshot) => { + const newPost = snapshot.val() || {}; + const post = { + id : postId, + ...newPost + } + dispatch(addPost(uid,post)); + }); + + } + } + } + + + /** + * Get all user posts from data base by user id + */ + export const dbGetPostsByUserId = (uid) => { + return (dispatch, getState) => { + + if (uid) { + var postsRef = firebaseRef.child(`userPosts/${uid}/posts`); + + return postsRef.once('value').then((snapshot) => { + var posts = snapshot.val() || {}; + var parsedPosts = {}; + Object.keys(posts).forEach((postId) => { + parsedPosts[postId]={ + id: postId, + ...posts[postId] + }; + }); + + dispatch(addPosts(uid,parsedPosts)); + }); + + } + } + } + + + + + + +/* _____________ CRUD State _____________ */ + +/** + * Add a normal post + * @param {string} uid is user identifier + * @param {object} post + */ + export const addPost = (uid,post) => { + return{ + type: types.ADD_POST, + payload: {uid,post} + } +} + +/** + * Update a post + * @param {string} uid is user identifier + * @param {object} post + */ + export const updatePost = (uid,post) => { + return{ + type: types.UPDATE_POST, + payload: {uid,post} + } +} + +/** + * Delete a post + * @param {string} uid is user identifier + * @param {string} id is post identifier + */ + export const deletePost = (uid,id) => { + return{ + type: types.DELETE_POST, + payload: {uid,id} + } +} + + +/** + * Add a list of post + * @param {string} uid + * @param {[object]} posts + */ +export const addPosts = (uid,posts) => { + return { + type: types.ADD_LIST_POST, + payload: {uid,posts} + } +} + +/** + * Clea all data in post store + */ +export const clearAllData = () => { + return{ + type: types.CLEAR_ALL_DATA_POST + } +} + +/** + * Add a post with image + * @param {object} post + */ +export const addImagePost = (uid,post) => { + return{ + type: types.ADD_IMAGE_POST, + payload: {uid,post} + } + +} + diff --git a/app/actions/userActions.jsx b/app/actions/userActions.jsx new file mode 100644 index 0000000..0199428 --- /dev/null +++ b/app/actions/userActions.jsx @@ -0,0 +1,192 @@ +// - Import react components +import {firebaseRef} from 'app/firebase/' + +// - Import action types +import * as types from 'actionTypes' + +// - Import actions +import * as globalActions from 'globalActions' +import * as userActions from 'userActions' + + + +/* _____________ CRUD DB _____________ */ + + +/** + * Get user info from database + */ +export const dbGetUserInfo = () => { + return (dispatch, getState) => { + var uid = getState().authorize.uid + if (uid) { + var userInfoRef = firebaseRef.child(`users/${uid}/info`); + + return userInfoRef.once('value').then((snapshot) => { + var userInfo = snapshot.val() || {}; + dispatch(addUserInfo(uid,userInfo)) + },error => console.log(error)); + + } + } +} + +/** + * Get user info from database + * @param {string} uid + */ +export const dbGetUserInfoByUserId = (uid,sw) => { + return (dispatch, getState) => { + if (uid) { + var userInfoRef = firebaseRef.child(`users/${uid}/info`); + + return userInfoRef.once('value').then((snapshot) => { + var userInfo = snapshot.val() || {}; + dispatch(addUserInfo(uid,userInfo)) + switch (sw) { + case 'header': + dispatch(globalActions.setHeaderTitle(userInfo.fullName)) + + break; + + default: + break; + } + },error => console.log(error)); + + } + } +} + +/** + * Updata user information + * @param {object} newInfo + */ +export const dbUpdateUserInfo = (newInfo) => { + return (dispatch,getState) => { + + // Get current user id + var uid = getState().authorize.uid + + // Write the new data simultaneously in the list + let updates = {}; + let info = getState().user.info[uid] + let updatedInfo = { + avatar: newInfo.avatar || info.avatar || '', + banner:newInfo.banner || info.banner || '', + email: newInfo.email || info.email || '', + fullName: newInfo.fullName || info.fullName || '', + tagLine: newInfo.tagLine || info.tagLine || '' + } + updates[`users/${uid}/info`] = updatedInfo + return firebaseRef.update(updates).then((result) => { + + dispatch(updateUserInfo(uid,updatedInfo)) + dispatch(closeEditProfile()) + }, (error) => { + dispatch(globalActions.showErrorMessage(error.message)) + }) + } + +} + +// - Get people info from database +export const dbGetPeopleInfo = () => { + return (dispatch, getState) => { + var uid = getState().authorize.uid + if (uid) { + var peopleRef = firebaseRef.child(`users`); + + return peopleRef.once('value').then((snapshot) => { + let people = snapshot.val() || {}; + + let parsedPeople = {}; + Object.keys(people).forEach((userId) => { + if(userId !== uid){ + parsedPeople[userId]={ + ...people[userId].info + }} + + }) + dispatch(addPeopleInfo(parsedPeople)) + },error => console.log(error)); + + } + } +} + +/* _____________ CRUD State _____________ */ + + +/** + * Add user information + * @param {string} uid is the user identifier + * @param {object} info is the information about user + */ +export const addUserInfo = (uid,info) => { + return{ + type: types.ADD_USER_INFO, + payload: {uid,info} + } +} + +/** + * Add people information + * @param {[object]} infoList is the lst of information about users + */ +export const addPeopleInfo = (infoList) => { + return{ + type: types.ADD_PEOPLE_INFO, + payload: infoList + } +} + +/** + * Update user information + * @param {string} uid is the user identifier + * @param {object} info is the information about user + */ + export const updateUserInfo = (uid,info) => { + return{ + type: types.UPDATE_USER_INFO, + payload: {uid,info} + } +} + +/** + * User info + * @param {object} info + */ +export const userInfo = (info) => { + return{ + type: types.USER_INFO, + info + } +} + +export const clearAllData = () => { + return { + type: types.CLEAR_ALL_DATA_USER + } +} + + +/** + * Open edit profile + */ +export const openEditProfile = () => { + return{ + type: types.OPEN_EDIT_PROFILE + } + +} + +/** + * Close edit profile + */ +export const closeEditProfile = () => { + return{ + type: types.CLOSE_EDIT_PROFILE + } + +} \ No newline at end of file diff --git a/app/actions/voteActions.jsx b/app/actions/voteActions.jsx new file mode 100644 index 0000000..8a317e0 --- /dev/null +++ b/app/actions/voteActions.jsx @@ -0,0 +1,130 @@ +// - Import react components +import {createAction as action} from 'redux-actions' +import moment from 'moment' +import { firebaseRef } from 'app/firebase/' + +// - Import action types +import * as types from 'actionTypes' + +// - Import actions +import * as globalActions from 'globalActions' +import * as notifyActions from 'notifyActions' + + +/* _____________ CRUD DB _____________ */ + +/** + * Add vote to database + * @param {string} postId is the identifier of the post which user vote + */ +export const dbAddVote = (postId,ownerPostUserId) => { + return (dispatch, getState) => { + + var uid = getState().authorize.uid + var vote = { + postId: postId, + creationDate: moment().unix(), + userDisplayName: getState().user.info[uid].fullName, + userAvatar: getState().user.info[uid].avatar, + userId: uid + } + + var voteRef = firebaseRef.child(`postVotes/${postId}`).push(vote) + return voteRef.then(() => { + dispatch(addVote( + { + vote, + postId: postId, + id: voteRef.key + })) + if(uid !== ownerPostUserId) + dispatch(notifyActions.dbAddNotify( + { + description:'Vote on your post.', + url:`/${ownerPostUserId}/posts/${postId}`, + notifyRecieverUserId:ownerPostUserId,notifierUserId:uid + })) + + }, (error) => dispatch(globalActions.showErrorMessage(error.message))) + + } +} + +/** + * Get all votes from database + */ +export const dbGetVotes = () => { + return (dispatch, getState) => { + var uid = getState().authorize.uid + if (uid) { + var votesRef = firebaseRef.child(`postVotes`); + + return votesRef.on('value',(snapshot) => { + var votes = snapshot.val() || {}; + dispatch(addVoteList(votes)) + }) + + } + } +} + + +/** + * 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) => { + return (dispatch, getState) => { + + // Get current user id + var uid = getState().authorize.uid + + // Write the new data simultaneously in the list + var updates = {}; + let votes = getState().vote.postVotes[postId] + let id = Object.keys(votes).filter((key)=> votes[key].userId === uid)[0] + console.log(' Id : ',id) + + updates[`postVotes/${postId}/${id}`] = null; + + return firebaseRef.update(updates).then((result) => { + dispatch(deleteVote({id, postId})) + }, (error) => dispatch(globalActions.showErrorMessage(error.message))) + } + +} + +/** + * Add a vote + * @param {object} data + */ +export const addVote = (data) => { + return { type: types.ADD_VOTE, payload: data } + +} + +/** + * delete a vote + * @param {object} data + */ +export const deleteVote = (data) => { + return { type: types.DELETE_VOTE, payload: data } + +} + +/** + * Ad a list of vote + * @param {object} data + */ +export const addVoteList = (data) => { + return { type: types.ADD_VOTE_LIST, payload: data } + +} + +/** + * Clear all data + */ +export const clearAllvotes = () => { + return { type: types.CLEAR_ALL_DATA_VOTE } +} diff --git a/app/api/AuthAPI.jsx b/app/api/AuthAPI.jsx new file mode 100644 index 0000000..d02e0dc --- /dev/null +++ b/app/api/AuthAPI.jsx @@ -0,0 +1,16 @@ +// - Import firebase components +import {firebaseAuth, firebaseRef} from 'app/firebase/' +import store from 'configureStore' + +// - Check user if is authorized +export var isAuthorized = () => { + var state = store.getState() + return state.authorize.authed + +} + +export var isAdmin = () =>{ + +return true; + +} diff --git a/app/api/AuthRouterAPI.jsx b/app/api/AuthRouterAPI.jsx new file mode 100644 index 0000000..0121b68 --- /dev/null +++ b/app/api/AuthRouterAPI.jsx @@ -0,0 +1,30 @@ +// - Import react components +import React, {Component} from 'react' +import {Route, Redirect} from 'react-router-dom' + +// - Import API +import * as AuthAPI from 'AuthAPI' + + +export var PrivateRoute = ({component: Component, ...rest}) => { + console.log('is auth ; ', AuthAPI.isAuthorized()); + return ( + AuthAPI.isAuthorized() + ? + : } + /> + ) +} + +export var PublicRoute = ({component: Component,...rest}) => { + return ( + !(AuthRouterAPI.isAuthorized()) + ? + : } + /> + ) +} diff --git a/app/api/CircleAPI.jsx b/app/api/CircleAPI.jsx new file mode 100644 index 0000000..2611d52 --- /dev/null +++ b/app/api/CircleAPI.jsx @@ -0,0 +1,51 @@ + + + +/** + * Get the circles which the specify users is in that circle + * @param {object} circles + * @param {string} followingId + */ +export const getUserBelongCircles = (circles,followingId) => { + let userBelongCircles = [] + 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) => { + let followingUsers = {} + 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 +} \ No newline at end of file diff --git a/app/api/FileAPI.jsx b/app/api/FileAPI.jsx new file mode 100644 index 0000000..3c08044 --- /dev/null +++ b/app/api/FileAPI.jsx @@ -0,0 +1,189 @@ +// - Import react component +import { storageRef } from 'app/firebase/' + +//- Import actions + +// - Get file Extension +const getExtension = (fileName) => { + var re = /(?:\.([^.]+))?$/; + return re.exec(fileName)[1]; +} + +// Converts image to canvas; returns new canvas element +const convertImageToCanvas = (image) => { + var canvas = document.createElement("canvas"); + canvas.width = image.width; + canvas.height = image.height; + canvas.getContext("2d").drawImage(image, 0, 0); + + return canvas; +} + +/** + * Constraint image size + * @param {file} file + * @param {number} maxWidth + * @param {number} maxHeight + */ +const constraintImage = (file,fileName, maxWidth, maxHeight) => { + // Ensure it's an image + if(file.type.match(/image.*/)) { + + // Load the image + var reader = new FileReader(); + reader.onload = function (readerEvent) { + var image = new Image(); + image.onload = function (imageEvent) { + + // Resize the image + var canvas = document.createElement('canvas'), + max_size = 986,// TODO : pull max size from a site config + width = image.width, + height = image.height; + if (width > height) { + if (width > max_size) { + height *= max_size / width; + width = max_size; + } + } else { + if (height > max_size) { + width *= max_size / height; + height = max_size; + } + } + canvas.width = width; + canvas.height = height; + canvas.getContext('2d').drawImage(image, 0, 0, width, height); + var dataUrl = canvas.toDataURL(); + var resizedImage = dataURLToBlob(dataUrl); + let evt = new CustomEvent('onSendResizedImage', { detail: {resizedImage,fileName} }); + window.dispatchEvent(evt); + + + } + image.src = readerEvent.target.result; + } + reader.readAsDataURL(file); + } +} + +// - Delete file from storage +const deleteFile = (folderName, fileName, callBackSuccess, callBackError) => { + + // Create a reference to the file to delete + var desertRef = storageRef.child(`${folderName}/${filename}`); + + // Delete the file + desertRef.delete().then(function () { + // File deleted successfully + callBackSuccess() + console.log('File has been deleted successfully'); + }).catch(function (error) { + // Uh-oh, an error occurred! + callBackError(error) + console.log(error); + }); + +} + +/** + * Convert data URL to blob + * @param {object} dataURL + */ +const dataURLToBlob = (dataURL) => { + + var BASE64_MARKER = ';base64,' + if (dataURL.indexOf(BASE64_MARKER) == -1) { + var parts = dataURL.split(',') + var contentType = parts[0].split(':')[1] + var raw = parts[1] + + return new Blob([raw], {type: contentType}) + } + + var parts = dataURL.split(BASE64_MARKER) + var contentType = parts[0].split(':')[1] + var raw = window.atob(parts[1]) + var rawLength = raw.length + + var uInt8Array = new Uint8Array(rawLength) + + for (var i = 0; i < rawLength; ++i) { + uInt8Array[i] = raw.charCodeAt(i) + } + + return new Blob([uInt8Array], {type: contentType}) +} + +// - Upload file +const uploadFile = (file, folderName, fileName, progressCallBack, errorCallBack, completeCallBack) => { + + // Create a storage refrence + var storegeFile = storageRef.child(folderName + '/' + fileName) + + // Upload file + var task = storegeFile.put(file) + + // Upload storage bar + task.on('state_changed', (snapshot) => { + var percentage = (snapshot.bytesTransferred / snapshot.totalBytes) * 100 + progressCallBack(percentage) + // Set uploader progress value + + }, (error) => { + errorCallBack(error) + }, (complete) => { + completeCallBack(complete) + }) +} + +const downloadFile = (folderName, fileName, callBack) => { + // Create a reference to the file we want to download + var starsRef = storageRef.child(`${folderName}/${fileName}`); + + + // Get the download URL + starsRef.getDownloadURL().then((url) => { + // Insert url into an tag to "download" + callBack(url) + }).catch((error) => { + + // A full list of error codes is available at + // https://firebase.google.com/docs/storage/web/handle-errors + switch (error.code) { + case 'storage/object_not_found': + // File doesn't exist + console.log('storage/object_not_found'); + break; + + case 'storage/unauthorized': + // User doesn't have permission to access the object + console.log('storage/unauthorized'); + break; + + case 'storage/canceled': + // User canceled the upload + console.log('storage/canceled'); + break; + + case 'storage/unknown': + // Unknown error occurred, inspect the server response + console.log('storage/unknown'); + break; + } + }); + +} + + + +export default { + downloadFile, + uploadFile, + dataURLToBlob, + deleteFile, + convertImageToCanvas, + getExtension, + constraintImage + +} \ No newline at end of file diff --git a/app/api/PostAPI.jsx b/app/api/PostAPI.jsx new file mode 100644 index 0000000..341c7bd --- /dev/null +++ b/app/api/PostAPI.jsx @@ -0,0 +1,29 @@ + +// Get tags from post content +export const detectTags = (content,character) => { + + return content.split(" ").filter((word) => { + return (word.slice(0,1) === character) + }) + +} +export const getContentTags = (content) => { + var newTags = [] + var tags = detectTags(content,'#') + tags.forEach((tag)=>{ + newTags.push(tag.slice(1)) + }) + return newTags +} + +export const sortObjectsDate = function (objects) { + var sortedObjects = objects; + + // Sort posts with creation date + sortedObjects.sort((a, b) => { + return parseInt(b.creationDate) - parseInt(a.creationDate) + + }); + + return sortedObjects; + } \ No newline at end of file diff --git a/app/app.jsx b/app/app.jsx new file mode 100644 index 0000000..2222baa --- /dev/null +++ b/app/app.jsx @@ -0,0 +1,56 @@ +// Import external components refrence +import React from 'react' +import ReactDOM from 'react-dom' +import injectTapEventPlugin from 'react-tap-event-plugin' +import { cyan500 } from 'material-ui/styles/colors' +import lightBaseTheme from 'material-ui/styles/baseThemes/lightBaseTheme' +import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider' +import getMuiTheme from 'material-ui/styles/getMuiTheme' + +import { Provider } from 'react-redux' +import store, { history } from 'configureStore' +import { ConnectedRouter } from 'react-router-redux' + +// - Import app components +import Master from 'Master' + +// - Impport actions +import * as authorizeActions from 'authorizeActions' +import * as globalActions from 'globalActions' +import * as userActions from 'userActions' + +// Set default data +store.subscribe(() => { + var state = store.getState(); + +}); + +// Needed for onTouchTap +// http://stackoverflow.com/a/34015469/988941 +injectTapEventPlugin(); + +// This replaces the textColor value on the palette +// and then update the keys for each component that depends on it. +// More on Colors: http://www.material-ui.com/#/customization/colors +const muiTheme = getMuiTheme({ + + +}); + + + +// App css +require('applicationStyles'); +const supportsHistory = 'pushState' in window.history + +ReactDOM.render( + + + + + + + + , + document.getElementById('app') +); diff --git a/app/components/Blog.jsx b/app/components/Blog.jsx new file mode 100644 index 0000000..60905e9 --- /dev/null +++ b/app/components/Blog.jsx @@ -0,0 +1,271 @@ +// - Import react components +import React, { Component } from 'react' +import { withRouter } from 'react-router-dom'; +import { connect } from 'react-redux' +import PropTypes from 'prop-types' +import { Card, CardActions, CardHeader, CardMedia, CardTitle, CardText } from 'material-ui/Card' +import FlatButton from 'material-ui/FlatButton' +import { grey400, grey800, darkBlack, lightBlack } from 'material-ui/styles/colors' +import SvgCamera from 'material-ui/svg-icons/image/photo-camera' +import Paper from 'material-ui/Paper' +import { List, ListItem } from 'material-ui/List' + + + +// - Import app components +import Post from 'Post' +import PostWrite from 'PostWrite' +import UserAvatar from 'UserAvatar' + +// - Import API +import * as AuthAPI from 'AuthAPI' +import * as PostAPI from 'PostAPI' + +// - Import actions +import * as globalActions from 'globalActions' + + +// - Create Blog component class +export class Blog extends Component { + + static propTypes = { + /** + * If it's true , writing post block will be visible + */ + displayWriting: PropTypes.bool.isRequired, + /** + * A list of post + */ + posts: PropTypes.object, + + } + + /** + * Component constructor + * @param {object} props is an object properties of component + */ + constructor(props) { + super(props) + + this.state = { + /** + * It's true if we want to have two column of posts + */ + divided: 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, + /** + * If it's true, post write will be open + */ + openPostWrite: false, + /** + * The title of home header + */ + homeTitle: PropTypes.string + } + + // Binding functions to `this` + this.postLoad = this.postLoad.bind(this) + this.handleOpenPostWrite = this.handleOpenPostWrite.bind(this) + this.handleClosePostWrite = this.handleClosePostWrite.bind(this) + + } + + /** + * Open post write + * + * + * @memberof Blog + */ + handleOpenPostWrite = () => { + this.setState({ + openPostWrite: true + }) + } + /** + * Close post write + * + * + * @memberof Blog + */ + handleClosePostWrite = () => { + this.setState({ + openPostWrite: false + }) +} + + /** + * Create a list of posts + * @return {DOM} posts + */ + postLoad = () => { + + let { posts,match } = this.props + let {tag} = match.params + if (posts === undefined || !Object.keys(posts).length > 0) { + + return ( + +

+ 'Nothing has shared.' +

+ + ) + } + else { + + var postBack = { oddPostList: [], evenPostList: [] } + var parsedPosts = [] + Object.keys(posts).forEach((postId) => { + if(tag){ + let regex = new RegExp("#" + tag,'g') + let postMatch = posts[postId].body.match(regex) + if(postMatch !==null) + parsedPosts.push({ ...posts[postId]}) + }else{ + parsedPosts.push({ ...posts[postId]}) + + } + }) + const sortedPosts = PostAPI.sortObjectsDate(parsedPosts) + if (sortedPosts.length > 6) { + postBack.divided = true + + } else { + postBack.divided = false + } + sortedPosts.forEach((post, index) => { + + var newPost = ( +
+ + {index > 1 || (!postBack.divided && index > 0) ?
: ''} + + +
+ ) + + if ((index % 2) === 1 && postBack.divided) { + postBack.oddPostList.push(newPost) + } + else { + postBack.evenPostList.push(newPost) + } + }) + return postBack + } + + } + + componentWillMount() { + const {setHomeTitle} = this.props + setHomeTitle() + } + + /** + * Reneder component DOM + * @return {react element} return the DOM which rendered by component + */ + render() { + + let postList = this.postLoad() + + const {tag, displayWriting, } = this.props + + return ( +
+ + +
+ +
+ {displayWriting && !tag + ? ( + + + What's new with you? } + leftAvatar={} + rightIcon={} + style={{ padding: "7px 0px", fontWeight: "200" }} + onTouchTap={this.handleOpenPostWrite} + /> + + +
+
) + : ''} + + {postList.evenPostList} +
+
+ {postList.divided + ? (
+
+ {postList.oddPostList} +
+
+
) + : ''} +
+ + +
+ ) + } +} + + +/** + * 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, ownProps) => { + return { + setHomeTitle: () => dispatch(globalActions.setHeaderTitle(ownProps.homeTitle || '')), + showTopLoading: () => dispatch(globalActions.showTopLoading()), + hideTopLoading: () => dispatch(globalActions.hideTopLoading()) + + } +} + +/** + * 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, ownProps) => { + return { + avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : '', + + } +} + +// - Connect component to redux store +export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Blog)) diff --git a/app/components/Circle.jsx b/app/components/Circle.jsx new file mode 100644 index 0000000..3f2e264 --- /dev/null +++ b/app/components/Circle.jsx @@ -0,0 +1,266 @@ +// - 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 '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 'UserAvatar' + + +// - Import API + + +// - Import actions +import * as circleActions from 'circleActions' + + + + + +/** +* Create component class + */ +export class Circle extends Component { + + static propTypes = { + /** + * Circle object + */ + circle: PropTypes.object.isRequired, + /** + * Circle identifier + */ + id: PropTypes.string.isRequired + + } + + /** + * Component constructor + * @param {object} props is an object properties of component + */ + constructor(props) { + 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 Circle + */ + handleChangeCircleName = (evt) => { + const {value} = evt.target + this.setState({ + circleName: value, + disabledSave: (!value || value.trim() === '') + }) + } + + /** + * Update user's circle + * + * + * @memberof Circle + */ + handleUpdateCircle = () => { + const {circleName} = this.state + if(circleName && circleName.trim() !== ''){ + this.props.updateCircle({name:circleName,id: this.props.id}) + } + } + + /** + * Handle delete circle + * + * + * @memberof Circle + */ + handleDeleteCircle = () => { + this.props.deleteCircle(this.props.id) + } + + /** + * Toggle circle to close/open + * + * + * @memberof Circle + */ + handleToggleCircle = () => { + this.setState({ + open: !this.state.open + }) + } + + + userList = () => { + const {users} = this.props.circle + const {userInfo} = this.props + let usersParsed =[] + + if(users){ + Object.keys(users).forEach((key, index) => { + const { fullName} = users[key] + let avatar = userInfo && userInfo[key] ? userInfo[key].avatar || '' : '' + usersParsed.push(} + onClick={()=> this.props.goTo(`/${key}`)} + />) + + }) + return usersParsed + } + } + /** + * Right icon menue of circle + * + * + * @memberof Circle + */ + rightIconMenu =( + + + + + ) + /** + * Reneder component DOM + * @return {react element} return the DOM which rendered by component + */ + render() { + + const circleTitle =( +
+
+
+ +
+
+ Circle settings +
+
+ +
+
+ +
+ ) + return ( +
+ {this.props.circle.name}} + leftIcon={} + rightIconButton={this.rightIconMenu} + initiallyOpen={false} + onClick={this.handleToggleCircle} + open={this.state.open} + nestedItems={this.userList()} + > + +
+ +
+
+
+
+ ) + } +} + + +/** + * 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, ownProps) => { + let {uid} = ownProps + return { + deleteCircle: (id) => dispatch(circleActions.dbDeleteCircle(id)), + updateCircle: (circle) => dispatch(circleActions.dbUpdateCircle(circle)), + closeCircleSettings: () => dispatch(circleActions.closeCircleSettings(uid,ownProps.id)), + openCircleSettings: () => dispatch(circleActions.openCircleSettings(uid,ownProps.id)), + goTo: (url)=> 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, ownProps) => { + 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)(Circle) \ No newline at end of file diff --git a/app/components/Comment.jsx b/app/components/Comment.jsx new file mode 100644 index 0000000..376c695 --- /dev/null +++ b/app/components/Comment.jsx @@ -0,0 +1,314 @@ +// - 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 { createAction as action } from 'redux-actions' +import moment from 'moment' +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 UserAvatar from 'UserAvatar' + + +// - Import API + +// - Import action types +import * as types from 'actionTypes' + + +// - Import actions +import * as commentActions from 'commentActions' +import * as userActions from 'userActions' + + + +/** + * Create component class + */ +export class Comment extends Component { + + + + 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, + } + + /** + * Component constructor + * @param {object} props is an object properties of component + */ + constructor(props) { + super(props) + + this.textareaRef = i => { this.inputText = i } + this.divCommentRef = i => { 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: PropTypes.bool + + } + + // 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) => { + 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) => { + + 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) => { + + 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) => { + 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, id, postId) => { + 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() { + + /** + * DOM styles + * + * + * @memberof Comment + */ + const 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" + + } + } + + /** + * Comment object from props + */ + const {comment} = this.props + + const iconButtonElement = ( + + + + ) + + + const RightIconMenu = () => ( + + Reply + {this.props.isCommentOwner ? (Edit): ''} + {(this.props.isCommentOwner || this.props.isPostOwner) ? ( this.handleDelete(evt, comment.id, comment.postId)}>Delete): ''} + + ) + + const Author = () => ( +
+ {comment.userDisplayName}{moment.unix(comment.creationDate).fromNow()} +
+ ) + const commentBody = ( +

{comment.text}

+ ) + + const {userId} = comment + + return ( +
+ +
+ + + {(!this.props.isCommentOwner && !this.props.isPostOwner && this.props.disableComments )? '' : ()} +
+ +
{this.state.text}
+
+
+
+ + +
+
+ +
+ ) + } +} + + +/** + * 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, ownProps) => { + return { + delete: (id, postId) => dispatch(commentActions.dbDeleteComment(id, postId)), + update: (id, postId, comment) => 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, ownProps) => { + const {uid} = state.authorize + const avatar = state.user.info && state.user.info[ownProps.comment.userId] ? state.user.info[ownProps.comment.userId].avatar || '' : '' + return { + uid: uid, + isCommentOwner: (uid === ownProps.comment.userId), + info: state.user.info, + avatar + + + } +} + +// - Connect component to redux store +export default connect(mapStateToProps, mapDispatchToProps)(Comment) diff --git a/app/components/CommentGroup.jsx b/app/components/CommentGroup.jsx new file mode 100644 index 0000000..5b49c37 --- /dev/null +++ b/app/components/CommentGroup.jsx @@ -0,0 +1,270 @@ +// - 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 'commentActions' + +// - Import app components +import CommentList from 'CommentList' +import CommentWrite from 'CommentWrite' +import UserAvatar from 'UserAvatar' + + +/** + * Create component class + */ +export class CommentGroup extends Component { + + +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 + +} + + /** + * Component constructor + * @param {object} props is an object properties of component + */ + constructor(props) { + 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, data) => { + 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 = () => { + var comments = this.props.comments + if (comments) { + + + var parsedComments = []; + 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 || '' : '' + + return (} + secondaryText={
+ + {comment.userDisplayName}: + + {comment.text} + +
} + secondaryTextLines={2} + /> + + ) + + }) + + } + } + + /** + * Reneder component DOM + * @return {react element} return the DOM which rendered by component + */ + render() { + + + return ( +
+
0 ? { display: "block" } : { display: "none" }}> + + + +
+ + +
+ {this.commentList()} + +
+
+
+ {(this.props.comments && Object.keys(this.props.comments).length > 0) + ? ( + + + ) : ''} + +
+ {!this.props.disableComments ? (
+ + + +
+ +
+ +
+
+ +
+
): ''} +
+ ); + } +} + +/** + * 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, ownProps) => { + return { + send: (text, postId, callBack) => { + dispatch(commentActions.dbAddComment({ + postId: postId, + text: text, + ownerPostUserId:ownProps.ownerPostUserId + }, 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, ownProps) => { + return { + comments: state.comment.postComments[ownProps.postId], + avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar || '' : '', + userInfo: state.user.info + + } +} + +// - Connect component to redux store +export default connect(mapStateToProps, mapDispatchToProps)(CommentGroup) diff --git a/app/components/CommentList.jsx b/app/components/CommentList.jsx new file mode 100644 index 0000000..597e90f --- /dev/null +++ b/app/components/CommentList.jsx @@ -0,0 +1,133 @@ +// - 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 Comment from 'Comment' +import * as PostAPI from 'PostAPI' + +// - Import actions + + + + +/** + * Create component class + */ +export class CommentList extends Component { + + 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) { + super(props) + + /** + * Default state + */ + this.state = { + + } + + // Binding functions to `this` + + } + + + + /** + * Get comments' DOM + * @return {DOM} list of comments' DOM + */ + commentList = () => { + var comments = this.props.comments + if (comments) { + + + var parsedComments = []; + Object.keys(comments).forEach((commentId) => { + parsedComments.push({ + id: commentId, + ...comments[commentId] + }); + }); + let sortedComments = PostAPI.sortObjectsDate(parsedComments) + + return sortedComments.map((comment, index, array) => { + + return + + }) + + } + } + + /** + * Reneder component DOM + * @return {react element} return the DOM which rendered by component + */ + render() { + + const styles = { + list: { + width: "100%", + maxHeight: 450, + overflowY: 'auto' + } + } + + return ( + + + + + {this.commentList()} + + ) + } +} + +/** + * 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, ownProps) => { + 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) => { + return { + + } +} + +// - Connect component to redux store +export default connect(mapStateToProps, mapDispatchToProps)(CommentList) diff --git a/app/components/CommentWrite.jsx b/app/components/CommentWrite.jsx new file mode 100644 index 0000000..4f8cd96 --- /dev/null +++ b/app/components/CommentWrite.jsx @@ -0,0 +1,84 @@ +// - Import react components +import React, {Component} from 'react' +import {connect} from 'react-redux' +import Faker from 'faker' + +// - Import app components + +// - Import actions +import * as commentActions from 'commentActions' + +// - Define variable +const buttonStyle = { + marginTop: '5px' +}; + +// - Create CommentWrite component class +export class CommentWrite extends Component { + constructor(props) { + super(props); + + this.state = { + inputValue:'' + } + + // Binding functions to `this` + this.handleRef = this.handleRef.bind(this); + this.focus = this.focus.bind(this); + this.handleAddComment = this.handleAddComment.bind(this); + this.handleOnChange = this.handleOnChange.bind(this) + + } + + handleOnChange = (evt) =>{ + this.setState({inputValue:evt.target.value}) + + } + + handleRef = c => { + this.inputRef = c + } + + focus = () => { + this.inputRef.focus() + } + handleAddComment = (evt) => { + this.props.send(this.state.inputValue,this.props.postId,this.props.close) + + } + +// Render DOM + render() { + return ( +
+