Merge pull request #1 from Qolzam/next

Updating to an actual version
This commit is contained in:
KaMeHb-UA
2018-06-14 16:48:43 +03:00
committed by GitHub
244 changed files with 16456 additions and 9323 deletions

View File

@@ -1,23 +1,37 @@
# Change Log # Change Log
## [Unreleased](https://github.com/Qolzam/react-social-network/tree/HEAD) ## [v0.6.0](https://github.com/Qolzam/react-social-network/tree/v0.6.0) (2018-03-25)
[Full Changelog](https://github.com/Qolzam/react-social-network/compare/v0.5.0...v0.6.0)
[Full Changelog](https://github.com/Qolzam/react-social-network/compare/v0.5.0...HEAD)
**Implemented enhancements:** **Implemented enhancements:**
- Better sourcemaps [\#27](https://github.com/Qolzam/react-social-network/issues/27) - Better sourcemaps [\#27](https://github.com/Qolzam/react-social-network/issues/27)
- \[Feature Request\] User account confirmation [\#23](https://github.com/Qolzam/react-social-network/issues/23) - \[Feature Request\] User account confirmation [\#23](https://github.com/Qolzam/react-social-network/issues/23)
- Feedback button doesn't do anything [\#20](https://github.com/Qolzam/react-social-network/issues/20) - Feedback button doesn't do anything [\#20](https://github.com/Qolzam/react-social-network/issues/20)
- \[Idea\] what about create react app instead of custom webpack [\#17](https://github.com/Qolzam/react-social-network/issues/17)
- \[discussion\] TypeScript or Create react app [\#15](https://github.com/Qolzam/react-social-network/issues/15)
- Forget password [\#9](https://github.com/Qolzam/react-social-network/issues/9) - Forget password [\#9](https://github.com/Qolzam/react-social-network/issues/9)
- Send feedback [\#6](https://github.com/Qolzam/react-social-network/issues/6) - Send feedback [\#6](https://github.com/Qolzam/react-social-network/issues/6)
- Pagination for posts [\#5](https://github.com/Qolzam/react-social-network/issues/5) - Pagination for posts [\#5](https://github.com/Qolzam/react-social-network/issues/5)
- Over layer on slidebar [\#4](https://github.com/Qolzam/react-social-network/issues/4)
**Fixed bugs:**
- \[BUG\] Provider icons not appear in login page browsing with Firefox [\#46](https://github.com/Qolzam/react-social-network/issues/46)
- Notifications popover not working properly on mobile [\#39](https://github.com/Qolzam/react-social-network/issues/39)
- \[BUG\] Real time notifications not work \(as in live example greensocial.herokuapp.com\) [\#30](https://github.com/Qolzam/react-social-network/issues/30)
**Closed issues:** **Closed issues:**
- Issue messages is for issue page not private message \[Edited by Qolzam\] [\#47](https://github.com/Qolzam/react-social-network/issues/47)
- Debugging with vs code on Windows machine [\#45](https://github.com/Qolzam/react-social-network/issues/45)
- The next version [\#42](https://github.com/Qolzam/react-social-network/issues/42) - The next version [\#42](https://github.com/Qolzam/react-social-network/issues/42)
- \[Feature request\] Change Password screen [\#36](https://github.com/Qolzam/react-social-network/issues/36) - \[Feature request\] Change Password screen [\#36](https://github.com/Qolzam/react-social-network/issues/36)
- What are your thoughts on TSX? [\#35](https://github.com/Qolzam/react-social-network/issues/35) - What are your thoughts on TSX? [\#35](https://github.com/Qolzam/react-social-network/issues/35)
- Question: auth reference vs auth\(\) execution [\#28](https://github.com/Qolzam/react-social-network/issues/28)
- How do you deploy the application? [\#26](https://github.com/Qolzam/react-social-network/issues/26)
- Data structure optimization [\#25](https://github.com/Qolzam/react-social-network/issues/25)
- Debounce in resizing [\#12](https://github.com/Qolzam/react-social-network/issues/12)
## [v0.5.0](https://github.com/Qolzam/react-social-network/tree/v0.5.0) (2018-01-14) ## [v0.5.0](https://github.com/Qolzam/react-social-network/tree/v0.5.0) (2018-01-14)
[Full Changelog](https://github.com/Qolzam/react-social-network/compare/v0.3.1...v0.5.0) [Full Changelog](https://github.com/Qolzam/react-social-network/compare/v0.3.1...v0.5.0)

View File

@@ -20,14 +20,21 @@ The structure of this project give the ability to developer to develop their pro
</a> </a>
</p> </p>
Since I started building this project I've planned to have a back end for this project so I haven't focused on performance until I build the back end and move some data procedure from end to back end. Therefore I need to change data structure and actions for [Redux](http://redux.js.org/). ## 🌟New Upgrade :
For those who prefer writing code by typescript, now React Social Network support both javascript and typescript language. React Social Network is [moving on](https://github.com/Qolzam/react-social-network/issues/48) [redux-saga](https://redux-saga.js.org/) however we keep [redux-thunk](https://github.com/gaearon/redux-thunk) version of **React Social Network** in branch name `v0.5`. Any contribution would be greatly appreciated by :heart:.
>You should consult the [CHANGELOG](https://github.com/Qolzam/react-social-network/blob/next/CHANGELOG.md) and related issues for more information >You should consult the [CHANGELOG](https://github.com/Qolzam/react-social-network/blob/next/CHANGELOG.md) and related issues for more information
This project adheres to the Contributor Covenant [code of conduct](https://github.com/Qolzam/react-social-network/blob/next/CODE_OF_CONDUCT.md). This project adheres to the Contributor Covenant [code of conduct](https://github.com/Qolzam/react-social-network/blob/next/CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable behavior to amir.gholzam@live.com. By participating, you are expected to uphold this code. Please report unacceptable behavior to amir.gholzam@live.com.
## Before Starting
First of all this is a boilerplate react social and the purpose is to find the best way to implement a huge project such as social network by [React](https://facebook.github.io/react/docs/hello-world.html) . We learn what technology or algorithm could be better solution for our project by [React](https://facebook.github.io/react/docs/hello-world.html). Please approach to this project with these ideas and if you feel that you have better solution, to our great pleasure if we could have your contribution.
- You are the person who is new to [React](https://facebook.github.io/react/docs/hello-world.html) and you are looking for some ideas to start [React](https://facebook.github.io/react/docs/hello-world.html) with some basic stuff. Also you are the fan of [React Semantic UI](https://react.semantic-ui.com/) I recommend you start with [React Blog Project](https://github.com/Qolzam/react-blog).
- You are the person who has the base knowledge of [React](https://facebook.github.io/react/docs/hello-world.html) which provided in [React Blog Project](https://github.com/Qolzam/react-blog) and you are the fan of social network projects, [Material-UI](http://www.material-ui.com/#/). You are the person who love JS/JSX. [js-react-social-tutorial](https://github.com/Qolzam/js-react-social-tutorial) project will be recommended.
- You are the fan of pure [Redux](http://redux.js.org/) with [redux-thunk](https://github.com/gaearon/redux-thunk), [TypeScript](https://www.typescriptlang.org/), [InversifyJS](http://inversify.io/) IOC container and you have the knowledge enough about the stuff in [js-react-social-tutorial](https://github.com/Qolzam/js-react-social-tutorial). [Version 0.5.0 branch of react-social-network](https://github.com/Qolzam/react-social-network/tree/v0.5) is the place you can find yourself.
- You are in advanced level of React and you are in love with learning advanced stuff about [React](https://facebook.github.io/react/docs/hello-world.html) such [Async Component/Lazy loading](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#code-splitting), [redux-saga](https://redux-saga.js.org/) and other cool stuff, well come you here. We are going ahead.👍
## Example ## Example
[Love Open Social](https://love-social.firebaseapp.com) [Love Open Social](https://love-social.firebaseapp.com)
@@ -40,7 +47,7 @@ I recommend that you get to know React before using React Social Network. React
## Document ## Document
Comming soon :) ... Comming soon :) ...
## Roadmap ## Roadmap
@@ -71,6 +78,8 @@ and then install the package
### Installing ### Installing
> You also have this [blog](https://medium.com/@qolzam/create-social-network-by-react-js-fe60010a32e6) which explain installation in details and for all level.
1. Fork the [react-social-network](https://github.com/Qolzam/react-social-network) repository on Github 1. Fork the [react-social-network](https://github.com/Qolzam/react-social-network) repository on Github
1. Clone your fork to your local machine 1. Clone your fork to your local machine
```bash ```bash
@@ -94,7 +103,7 @@ and then install the package
**Video tutorial** **Video tutorial**
[![Install React Social Network](https://img.youtube.com/vi/zrqDE82Eny8/0.jpg)](https://www.youtube.com/watch?v=zrqDE82Eny8) [![Install React Social Network](https://img.youtube.com/vi/zrqDE82Eny8/0.jpg)](https://www.youtube.com/watch?v=zrqDE82Eny8)
#### [Firestore Social Backend](https://github.com/Qolzam/firestore-social-backend) #### With [Firestore](https://github.com/Qolzam/firestore-social-backend)
* Configure firebase: * Configure firebase:
* If you don't have firebase account, follow [Create firebase account](https://firebase.google.com/) * If you don't have firebase account, follow [Create firebase account](https://firebase.google.com/)
* Create Project open the [Firebase Console](https://console.firebase.google.com/) and create a new project. * Create Project open the [Firebase Console](https://console.firebase.google.com/) and create a new project.
@@ -104,14 +113,14 @@ and then install the package
* [Enable Email/Password Authentication](https://firebase.google.com/docs/auth/web/password-auth) sign-in on firebase: * [Enable Email/Password Authentication](https://firebase.google.com/docs/auth/web/password-auth) sign-in on firebase:
* In the Firebase console, open the Auth section. * In the Firebase console, open the Auth section.
* On the Sign in method tab, enable the Email/password sign-in method and click Save. * On the Sign in method tab, enable the Email/password sign-in method and click Save.
* [Enable OAuth](https://firebase.google.com/docs/auth/) We are supporting sign-in with Github, Google and Facebook. Following [firebase document](https://firebase.google.com/docs/auth/) you can enable each one you need. * [Enable OAuth](https://firebase.google.com/docs/auth/) We are supporting sign-in with [Github](https://medium.com/@endactiongroup/enable-github-sign-in-oauth-with-firebase-38b93960e8db), Google and [Facebook](https://medium.com/@endactiongroup/enable-facebook-sign-in-oauth-with-firebase-af7a6651b60c). Following [firebase document](https://firebase.google.com/docs/auth/) you can enable each one you need.
* [Install Firestore Social Backend](https://github.com/Qolzam/firestore-social-backend) Follow instruction of [Firestore Social Backend](https://github.com/Qolzam/firestore-social-backend) * [Install Firestore Social Backend](https://github.com/Qolzam/firestore-social-backend) Follow instruction of [Firestore Social Backend](https://github.com/Qolzam/firestore-social-backend)
* Enable firestore dependencies * Enable firestore dependencies
* Go to React Social Network folder in `src/socialEngine.ts` write `useFirestore(provider)` to enable firestore dependencies! * Go to React Social Network folder in `src/socialEngine.ts` write `useFirestore(provider)` to enable firestore dependencies!
#### AWS Social Backend #### With AWS
* Coming soon ... * Coming soon ...
#### [ASP.NET Social Backend](https://github.com/Qolzam/aspnet-core-social-network) #### With [ASP.NET](https://github.com/Qolzam/aspnet-core-social-network)
* Coming soon ... * Coming soon ...
1. Go ahead ;) 1. Go ahead ;)
```bash ```bash
@@ -138,6 +147,8 @@ There are three main layers:
- Using [InversifyJS](http://inversify.io/) in project give us the ability to switch between custom dependencies easily. Specially for *data layer*, if you are the user working with [AWS](https://aws.amazon.com/) you only need to call `useAws()` or using [Firebase](https://firebase.google.com/) call `useFirestore()` in [SocialEngine](https://github.com/Qolzam/react-social-network/blob/next/src/socialEngine.ts#L20) file. - Using [InversifyJS](http://inversify.io/) in project give us the ability to switch between custom dependencies easily. Specially for *data layer*, if you are the user working with [AWS](https://aws.amazon.com/) you only need to call `useAws()` or using [Firebase](https://firebase.google.com/) call `useFirestore()` in [SocialEngine](https://github.com/Qolzam/react-social-network/blob/next/src/socialEngine.ts#L20) file.
### Features ### Features
- We moved from custom webpack to [create-react-app](https://github.com/facebook/create-react-app). - We moved from custom webpack to [create-react-app](https://github.com/facebook/create-react-app).
- Moving on [redux-saga](https://redux-saga.js.org/) managing async request and side effects.
- Supporting [Async Component/Lazy loading](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#code-splitting) for each container.
- Support Localization by [react-localize-redux](https://github.com/ryandrewjohnson/react-localize-redux). Providing this feature we support variety of languages. To contribute :heart: your language you are able to add your local language. You only need to edit `en.json` from `react-social-network` root project and `src/locale/en.json`. You should name your file according ISO 639-1 Language Codes. For example for Spanish you should name `es.json`. Which `es` is the standard code of Spanish language. - Support Localization by [react-localize-redux](https://github.com/ryandrewjohnson/react-localize-redux). Providing this feature we support variety of languages. To contribute :heart: your language you are able to add your local language. You only need to edit `en.json` from `react-social-network` root project and `src/locale/en.json`. You should name your file according ISO 639-1 Language Codes. For example for Spanish you should name `es.json`. Which `es` is the standard code of Spanish language.
- [InversifyJS](http://inversify.io/) as IOC container - [InversifyJS](http://inversify.io/) as IOC container
- Add auto compile on changing code for `webpack` - Add auto compile on changing code for `webpack`
@@ -145,6 +156,7 @@ There are three main layers:
- Add reset password, confirm password and authorizing by GitHub, Google and Facebook. - Add reset password, confirm password and authorizing by GitHub, Google and Facebook.
- Add scroll auto loading for show posts and people pages. - Add scroll auto loading for show posts and people pages.
- Using [Firestore](https://firebase.google.com/docs/firestore/) - Using [Firestore](https://firebase.google.com/docs/firestore/)
- Supportig `Right To Left`
- Some cool stuff :) - Some cool stuff :)
## Can I connect React Social Network to other data platforms ? :bowtie: ## Can I connect React Social Network to other data platforms ? :bowtie:
Your server side is on `PHP`, `Java`,`ASP.NET`, `Python`, etc. Or you are using serverless platforms such as `Google Cloud`, `AWS`, `Azure`, etc. You can connect `React Social Network` to any data platform. You only need to implement the [interfaces of core services](https://github.com/Qolzam/react-social-network/tree/next/src/core/services) like implementation of [firestoreClient](https://github.com/Qolzam/react-social-network/tree/next/src/data/firestoreClient). Your server side is on `PHP`, `Java`,`ASP.NET`, `Python`, etc. Or you are using serverless platforms such as `Google Cloud`, `AWS`, `Azure`, etc. You can connect `React Social Network` to any data platform. You only need to implement the [interfaces of core services](https://github.com/Qolzam/react-social-network/tree/next/src/core/services) like implementation of [firestoreClient](https://github.com/Qolzam/react-social-network/tree/next/src/data/firestoreClient).
@@ -172,6 +184,7 @@ There are three main layers:
```bash ```bash
npm run deploy:firebase npm run deploy:firebase
``` ```
- Please checkout [here](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#deployment) for more deployment solution.
## Built With ## Built With
@@ -182,6 +195,7 @@ There are three main layers:
* [Material-UI](http://www.material-ui.com/#/) A Set of React Components that Implement Google's Material Design. * [Material-UI](http://www.material-ui.com/#/) A Set of React Components that Implement Google's Material Design.
* [react-redux](https://github.com/reactjs/react-redux) Official React bindings for Redux. * [react-redux](https://github.com/reactjs/react-redux) Official React bindings for Redux.
* [Firebase](https://firebase.google.com/) products like Analytics, Realtime Database, Messaging, and Crash Reporting let you move quickly and focus on your users. * [Firebase](https://firebase.google.com/) products like Analytics, Realtime Database, Messaging, and Crash Reporting let you move quickly and focus on your users.
* [redux-saga](https://redux-saga.js.org/) is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, simple to test, and better at handling failures.
* [redux-thunk](https://github.com/gaearon/redux-thunk) Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters. * [redux-thunk](https://github.com/gaearon/redux-thunk) Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.
* [React Router V4](https://github.com/ReactTraining/react-router) for routing website location * [React Router V4](https://github.com/ReactTraining/react-router) for routing website location
* [Sass](http://sass-lang.com/) CSS with superpowers. Sass boasts more features and abilities than any other CSS extension language out there. * [Sass](http://sass-lang.com/) CSS with superpowers. Sass boasts more features and abilities than any other CSS extension language out there.
@@ -203,6 +217,9 @@ We use [SemVer](http://semver.org/) for versioning. For the versions available,
- Amir Movahedi - Amir Movahedi
- See also the list of [contributors](https://github.com/Qolzam/react-social-network/contributors) who participated in this project. - See also the list of [contributors](https://github.com/Qolzam/react-social-network/contributors) who participated in this project.
## How To Support
- [Contribution](https://github.com/Qolzam/react-social-network/blob/next/CONTRIBUTING.md)
- Fork || Star
## License ## License
This project is licensed under the MIT License - see the [LICENSE](https://github.com/Qolzam/react-social-network/blob/next/LICENSE) file for details This project is licensed under the MIT License - see the [LICENSE](https://github.com/Qolzam/react-social-network/blob/next/LICENSE) file for details

View File

@@ -20,14 +20,21 @@ The structure of this project give the ability to developer to develop their pro
</a> </a>
</p> </p>
Since I started building this project I've planned to have a back end for this project so I haven't focused on performance until I build the back end and move some data procedure from end to back end. Therefore I need to change data structure and actions for [Redux](http://redux.js.org/). ## 🌟New Upgrade :
For those who prefer writing code by typescript, now React Social Network support both javascript and typescript language. React Social Network is [moving on](https://github.com/Qolzam/react-social-network/issues/48) [redux-saga](https://redux-saga.js.org/) however we keep [redux-thunk](https://github.com/gaearon/redux-thunk) version of **React Social Network** in branch name `v0.5`. Any contribution would be greatly appreciated by :heart:.
>You should consult the [CHANGELOG](https://github.com/Qolzam/react-social-network/blob/next/CHANGELOG.md) and related issues for more information >You should consult the [CHANGELOG](https://github.com/Qolzam/react-social-network/blob/next/CHANGELOG.md) and related issues for more information
This project adheres to the Contributor Covenant [code of conduct](https://github.com/Qolzam/react-social-network/blob/next/CODE_OF_CONDUCT.md). This project adheres to the Contributor Covenant [code of conduct](https://github.com/Qolzam/react-social-network/blob/next/CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable behavior to amir.gholzam@live.com. By participating, you are expected to uphold this code. Please report unacceptable behavior to amir.gholzam@live.com.
## Before Starting
First of all this is a boilerplate react social and the purpose is to find the best way to implement a huge project such as social network by [React](https://facebook.github.io/react/docs/hello-world.html) . We learn what technology or algorithm could be better solution for our project by [React](https://facebook.github.io/react/docs/hello-world.html). Please approach to this project with these ideas and if you feel that you have better solution, to our great pleasure if we could have your contribution.
- You are the person who is new to [React](https://facebook.github.io/react/docs/hello-world.html) and you are looking for some ideas to start [React](https://facebook.github.io/react/docs/hello-world.html) with some basic stuff. Also you are the fan of [React Semantic UI](https://react.semantic-ui.com/) I recommend you start with [React Blog Project](https://github.com/Qolzam/react-blog).
- You are the person who has the base knowledge of [React](https://facebook.github.io/react/docs/hello-world.html) which provided in [React Blog Project](https://github.com/Qolzam/react-blog) and you are the fan of social network projects, [Material-UI](http://www.material-ui.com/#/). You are the person who love JS/JSX. [js-react-social-tutorial](https://github.com/Qolzam/js-react-social-tutorial) project will be recommended.
- You are the fan of pure [Redux](http://redux.js.org/) with [redux-thunk](https://github.com/gaearon/redux-thunk), [TypeScript](https://www.typescriptlang.org/), [InversifyJS](http://inversify.io/) IOC container and you have the knowledge enough about the stuff in [js-react-social-tutorial](https://github.com/Qolzam/js-react-social-tutorial). [Version 0.5.0 branch of react-social-network](https://github.com/Qolzam/react-social-network/tree/v0.5) is the place you can find yourself.
- You are in advanced level of React and you are in love with learning advanced stuff about [React](https://facebook.github.io/react/docs/hello-world.html) such [Async Component/Lazy loading](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#code-splitting), [redux-saga](https://redux-saga.js.org/) and other cool stuff, well come you here. We are going ahead.👍
## Example ## Example
[Love Open Social](https://love-social.firebaseapp.com) [Love Open Social](https://love-social.firebaseapp.com)
@@ -40,7 +47,7 @@ I recommend that you get to know React before using React Social Network. React
## Document ## Document
Comming soon :) ... Comming soon :) ...
## Roadmap ## Roadmap
@@ -138,6 +145,8 @@ There are three main layers:
- Using [InversifyJS](http://inversify.io/) in project give us the ability to switch between custom dependencies easily. Specially for *data layer*, if you are the user working with [AWS](https://aws.amazon.com/) you only need to call `useAws()` or using [Firebase](https://firebase.google.com/) call `useFirestore()` in [SocialEngine](https://github.com/Qolzam/react-social-network/blob/next/src/socialEngine.ts#L20) file. - Using [InversifyJS](http://inversify.io/) in project give us the ability to switch between custom dependencies easily. Specially for *data layer*, if you are the user working with [AWS](https://aws.amazon.com/) you only need to call `useAws()` or using [Firebase](https://firebase.google.com/) call `useFirestore()` in [SocialEngine](https://github.com/Qolzam/react-social-network/blob/next/src/socialEngine.ts#L20) file.
### Features ### Features
- We moved from custom webpack to [create-react-app](https://github.com/facebook/create-react-app). - We moved from custom webpack to [create-react-app](https://github.com/facebook/create-react-app).
- Moving on [redux-saga](https://redux-saga.js.org/) managing async request and side effects.
- Supporting [Async Component/Lazy loading](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#code-splitting) for each container.
- Support Localization by [react-localize-redux](https://github.com/ryandrewjohnson/react-localize-redux). Providing this feature we support variety of languages. To contribute :heart: your language you are able to add your local language. You only need to edit `en.json` from `react-social-network` root project and `src/locale/en.json`. You should name your file according ISO 639-1 Language Codes. For example for Spanish you should name `es.json`. Which `es` is the standard code of Spanish language. - Support Localization by [react-localize-redux](https://github.com/ryandrewjohnson/react-localize-redux). Providing this feature we support variety of languages. To contribute :heart: your language you are able to add your local language. You only need to edit `en.json` from `react-social-network` root project and `src/locale/en.json`. You should name your file according ISO 639-1 Language Codes. For example for Spanish you should name `es.json`. Which `es` is the standard code of Spanish language.
- [InversifyJS](http://inversify.io/) as IOC container - [InversifyJS](http://inversify.io/) as IOC container
- Add auto compile on changing code for `webpack` - Add auto compile on changing code for `webpack`
@@ -145,6 +154,7 @@ There are three main layers:
- Add reset password, confirm password and authorizing by GitHub, Google and Facebook. - Add reset password, confirm password and authorizing by GitHub, Google and Facebook.
- Add scroll auto loading for show posts and people pages. - Add scroll auto loading for show posts and people pages.
- Using [Firestore](https://firebase.google.com/docs/firestore/) - Using [Firestore](https://firebase.google.com/docs/firestore/)
- Supportig `Right To Left`
- Some cool stuff :) - Some cool stuff :)
## Can I connect React Social Network to other data platforms ? :bowtie: ## Can I connect React Social Network to other data platforms ? :bowtie:
Your server side is on `PHP`, `Java`,`ASP.NET`, `Python`, etc. Or you are using serverless platforms such as `Google Cloud`, `AWS`, `Azure`, etc. You can connect `React Social Network` to any data platform. You only need to implement the [interfaces of core services](https://github.com/Qolzam/react-social-network/tree/next/src/core/services) like implementation of [firestoreClient](https://github.com/Qolzam/react-social-network/tree/next/src/data/firestoreClient). Your server side is on `PHP`, `Java`,`ASP.NET`, `Python`, etc. Or you are using serverless platforms such as `Google Cloud`, `AWS`, `Azure`, etc. You can connect `React Social Network` to any data platform. You only need to implement the [interfaces of core services](https://github.com/Qolzam/react-social-network/tree/next/src/core/services) like implementation of [firestoreClient](https://github.com/Qolzam/react-social-network/tree/next/src/data/firestoreClient).
@@ -172,6 +182,7 @@ There are three main layers:
```bash ```bash
npm run deploy:firebase npm run deploy:firebase
``` ```
- Please checkout [here](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#deployment) for more deployment solution.
## Built With ## Built With
@@ -182,6 +193,7 @@ There are three main layers:
* [Material-UI](http://www.material-ui.com/#/) A Set of React Components that Implement Google's Material Design. * [Material-UI](http://www.material-ui.com/#/) A Set of React Components that Implement Google's Material Design.
* [react-redux](https://github.com/reactjs/react-redux) Official React bindings for Redux. * [react-redux](https://github.com/reactjs/react-redux) Official React bindings for Redux.
* [Firebase](https://firebase.google.com/) products like Analytics, Realtime Database, Messaging, and Crash Reporting let you move quickly and focus on your users. * [Firebase](https://firebase.google.com/) products like Analytics, Realtime Database, Messaging, and Crash Reporting let you move quickly and focus on your users.
* [redux-saga](https://redux-saga.js.org/) is a library that aims to make application side effects (i.e. asynchronous things like data fetching and impure things like accessing the browser cache) easier to manage, more efficient to execute, simple to test, and better at handling failures.
* [redux-thunk](https://github.com/gaearon/redux-thunk) Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters. * [redux-thunk](https://github.com/gaearon/redux-thunk) Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.
* [React Router V4](https://github.com/ReactTraining/react-router) for routing website location * [React Router V4](https://github.com/ReactTraining/react-router) for routing website location
* [Sass](http://sass-lang.com/) CSS with superpowers. Sass boasts more features and abilities than any other CSS extension language out there. * [Sass](http://sass-lang.com/) CSS with superpowers. Sass boasts more features and abilities than any other CSS extension language out there.

View File

@@ -5,7 +5,7 @@
## Layers ## Layers
* [App](layers/app.md) * [Overview](layers.md)
* [Components](layers/components.md) * [Components](layers/components.md)
* [API](layers/api.md) * [API](layers/api.md)
* [Actions](layers/actions.md) * [Actions](layers/actions.md)

BIN
docs/app/layer-1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

BIN
docs/app/layer-2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

BIN
docs/app/layer-3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

BIN
docs/app/layer-4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

BIN
docs/app/layer-5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

BIN
docs/app/layer-6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

BIN
docs/app/layer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

28
docs/layers.md Normal file
View File

@@ -0,0 +1,28 @@
# Layers
<img height="400" width="700" src="https://raw.githubusercontent.com/Qolzam/react-social-network/next/docs/app/layer.png">
## Supporting multiple data platforms.
With dependency injection and new structure we have tried to support variety of data platforms such as Google Cloud Firebase, AWS, Azure or using backend such as ASP.NET, PHP, JAVA, etc.
<img height="400" width="700" src="https://raw.githubusercontent.com/Qolzam/react-social-network/next/docs/app/layer-1.png">
## Easy and fast to scale in structure
Layers are organized in the way we have easy and less changes for adding/removing features. It could be integrated with server side too. As a result we are faster in developing in both side.
### Core
- The infrastructure,
- Providing interfaces for services
- Domain
### Data
* Firebase Data Client
* AWS Data Client
* Azure Data Client
* ASP.NET Data Client
* PHP Data Client
### Components
<img height="400" width="700" src="https://raw.githubusercontent.com/Qolzam/react-social-network/next/docs/app/layer-3.png">
## Integrating and reusability in both mobile and web app
With new structure we are able to develop the mobile app in parallel with web app **only** with changing `Components` layer. It means we can keep `Core`, `Data` layers, `Actions`, `Reducers`, etc. What we have high reusability and fast in producing the products.
<img height="400" width="700" src="https://raw.githubusercontent.com/Qolzam/react-social-network/next/docs/app/layer-5.png">

View File

@@ -1,53 +1,3 @@
# API # API
Is a decoupled layer of interfaces to data and/or functionality of one or more components. Is a decoupled layer of interfaces to data and/or functionality of one or more components.
## FileAPI
A set of functions for working with files.
```javascript
getExtension = (fileName) => {}
```
Get file extension from file name. `fileName` is the name of file.
```javascript
convertImageToCanvas = (image) => {}
```
Conver image to canvas. `image` is the image file should be converted.
```javascript
constraintImage = (file,fileName, maxWidth, maxHeight) => {}
```
Resize an image with specific max-height and max-with. After resizing image, `onSendResizedImage` event will be fired and will send the image resized and file name as arguments . `file` is image file , `fileName` is the name of the image, `maxWidth` is the maximum width and `maxHeight` is the maximum width for the image.
```javascript
dataURLToBlob = (dataURL) => {}
```
Convert [data url file](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) to [blob file](https://developer.mozilla.org/en/docs/Web/API/Blob). `dataURL` is the file with data url type.
## PostAPI
A set of functions for working with user posts.
```javascript
detectTags = (content,character) => {}
```
Detect and get word wich starts with specific `character` in the `content` of a text.
```javascript
getContentTags = (content) => {}
```
Get tag from the `content` of a text.
```javascript
sortObjectsDate = (objects) => {}
```
Sort an java script object based on key/value type.

View File

@@ -1,7 +0,0 @@
# App
App layer is included whole main application libraries and components.
## app
The root component that is responsible for rendering the whole React DOM on the browser and connecting components to redux [store](http://redux.js.org/docs/api/Store.html).

View File

@@ -1,60 +1,3 @@
# Components # Components
This layer include [React components](https://facebook.github.io/react/docs/react-component.html) that let you split the UI into independent, reusable pieces, and think about each piece in isolation. This layer include [React components](https://facebook.github.io/react/docs/react-component.html) that let you split the UI into independent, reusable pieces, and think about each piece in isolation.
## Add a new component
To add a new page in Home (with side-bar and top-bar) :
- In components/Home
- Inside the [Switch] tag
```jsx
<SidebarMain>
<Switch>
{/* Add your <Route/> here! */}
</Switch>
</SidebarMain>
```
- Add your component route between [Switch] tag to load in home page
```javascript
<Route path="/route-name" component={ComponentName} />
```
To add a new item for menu bar
- In components/Home
- Inside the [Menu] tag
```javascript
<SidebarContent>
<Menu style={{ color: "rgb(117, 117, 117)", width: '210px' }}>
{/* Add you <NavLink /> here */}
</Menu>
</SidebarContent>
```
- Add your component NavLink between [Menu] tag
```javascript
<NavLink to='/route-name'>
<MenuItem primaryText="MenuItemName" style={{ color: "rgb(117, 117, 117)" }} leftIcon={<SvgIcon />} />
{/* Other menu itemes */}
</NavLink>
```
*Note: You can choose your icon for [SvgIcon] from [material-ui icons](http://www.material-ui.com/#/components/svg-icon)*
To add your page in Master page (without side-bar menu and top-bar)
- In components/Master/Master.tsx
- Add your [Route] tag between [Switch] tag
```javascript
<Switch>
<Route path='/route-name' component={ComponentName} />
{/* Other routes */}
</Switch>
```

View File

@@ -1,6 +1,35 @@
{ {
"hosting": { "hosting": {
"public": "build", "public": "build",
"headers": [
{
"source": "**/*.@(eot|otf|ttf|ttc|woff|font.css)",
"headers": [
{
"key": "Access-Control-Allow-Origin",
"value": "*"
}
]
},
{
"source": "**/*.@(jpg|jpeg|gif|png)",
"headers": [
{
"key": "Cache-Control",
"value": "max-age=7200"
}
]
},
{
"source": "**/*.js",
"headers": [
{
"key": "Cache-Control",
"value": "no-cache"
}
]
}
],
"rewrites": [ "rewrites": [
{ {
"source": "/favicon.ico", "source": "/favicon.ico",

12767
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,26 +13,32 @@
"build": "npm-run-all build-css build-js", "build": "npm-run-all build-css build-js",
"test": "react-scripts-ts test --env=jsdom", "test": "react-scripts-ts test --env=jsdom",
"eject": "react-scripts-ts eject", "eject": "react-scripts-ts eject",
"watch": "webpack -w",
"deploy:firebase": "npm run build && firebase deploy" "deploy:firebase": "npm run build && firebase deploy"
}, },
"author": "Amir Movahedi", "author": "Amir Movahedi",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"amazon-cognito-identity-js": "^1.21.0", "@material-ui/core": "^1.0.0-rc.1",
"@material-ui/icons": "^1.0.0-rc.0",
"@types/react-helmet": "^5.0.5",
"@types/redux-devtools": "^3.0.43",
"@types/redux-devtools-dock-monitor": "^1.1.32",
"@types/redux-devtools-log-monitor": "^1.0.33",
"@types/redux-immutable": "^3.0.38",
"amazon-cognito-identity-js": "^2.0.0",
"aws-sdk": "^2.132.0", "aws-sdk": "^2.132.0",
"axios": "^0.16.2", "axios": "^0.16.2",
"classnames": "^2.2.5", "classnames": "^2.2.5",
"copy-to-clipboard": "^3.0.8", "copy-to-clipboard": "^3.0.8",
"crypto-js": "^3.1.9-1", "crypto-js": "^3.1.9-1",
"faker": "^4.1.0", "faker": "^4.1.0",
"firebase": "^4.6.2", "firebase": "^4.11.0",
"immutable": "^3.8.2",
"install": "^0.10.2", "install": "^0.10.2",
"inversify": "^4.6.0", "inversify": "^4.6.0",
"jss-rtl": "^0.2.1",
"keycode": "^2.1.9", "keycode": "^2.1.9",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"material-ui": "^1.0.0-beta.33",
"material-ui-icons": "^1.0.0-beta.17",
"moment": "^2.18.1", "moment": "^2.18.1",
"morgan": "^1.8.1", "morgan": "^1.8.1",
"node-sass-chokidar": "0.0.3", "node-sass-chokidar": "0.0.3",
@@ -42,21 +48,26 @@
"react": "^16.2.0", "react": "^16.2.0",
"react-addons-test-utils": "^15.6.2", "react-addons-test-utils": "^15.6.2",
"react-avatar-editor": "^10.3.0", "react-avatar-editor": "^10.3.0",
"react-day-picker": "^7.0.7",
"react-dom": "^16.2.0", "react-dom": "^16.2.0",
"react-event-listener": "^0.5.1", "react-event-listener": "^0.5.1",
"react-helmet": "^5.2.0",
"react-infinite-scroller": "^1.1.2", "react-infinite-scroller": "^1.1.2",
"react-linkify": "^0.2.1", "react-linkify": "^0.2.1",
"react-localize-redux": "^2.15.1", "react-loadable": "^5.3.1",
"react-parallax": "^1.4.4", "react-localize-redux": "^2.17.2",
"react-parallax": "^1.6.1",
"react-redux": "^5.0.6", "react-redux": "^5.0.6",
"react-router": "^4.1.1 ", "react-router": "^4.1.1 ",
"react-router-dom": "^4.1.1", "react-router-dom": "^4.1.1",
"react-router-redux": "^5.0.0-alpha.6", "react-router-redux": "^5.0.0-alpha.6",
"react-scripts-ts": "2.13.0", "react-share": "^2.0.0",
"react-string-replace": "^0.4.0", "react-string-replace": "^0.4.0",
"react-tap-event-plugin": "^3.0.2", "react-tap-event-plugin": "^3.0.2",
"redux": "^3.7.2", "redux": "^3.7.2",
"redux-actions": "^2.0.3", "redux-actions": "^2.0.3",
"redux-immutable": "^4.0.0",
"redux-saga": "^0.16.0",
"redux-thunk": "^2.2.0", "redux-thunk": "^2.2.0",
"reflect-metadata": "^0.1.10", "reflect-metadata": "^0.1.10",
"save": "^2.3.0", "save": "^2.3.0",
@@ -67,7 +78,7 @@
"@types/classnames": "^2.2.3", "@types/classnames": "^2.2.3",
"@types/jest": "^22.1.1", "@types/jest": "^22.1.1",
"@types/lodash": "^4.14.77", "@types/lodash": "^4.14.77",
"@types/node": "^9.4.0", "@types/node": "^9.6.2",
"@types/prop-types": "^15.5.2", "@types/prop-types": "^15.5.2",
"@types/react": "^16.0.36", "@types/react": "^16.0.36",
"@types/react-dom": "^16.0.3", "@types/react-dom": "^16.0.3",
@@ -79,15 +90,39 @@
"@types/react-tap-event-plugin": "0.0.30", "@types/react-tap-event-plugin": "0.0.30",
"@types/redux-logger": "^3.0.4", "@types/redux-logger": "^3.0.4",
"@types/uuid": "^3.4.3", "@types/uuid": "^3.4.3",
"redux-logger": "^3.0.1", "react-scripts-ts": "^2.14.0",
"redux-devtools": "^3.4.1",
"redux-devtools-dock-monitor": "^1.1.3",
"redux-devtools-extension": "^2.13.2",
"redux-devtools-log-monitor": "^1.4.0",
"redux-logger": "^3.0.6",
"redux-mock-store": "^1.2.3", "redux-mock-store": "^1.2.3",
"ts-node": "^3.3.0", "ts-node": "^5.0.1",
"tslint": "^5.7.0", "tslint": "^5.9.1",
"tslint-config-standard": "^6.0.1", "tslint-config-standard": "^6.0.1",
"typescript": "^2.7.1" "typescript": "^2.8.1"
}, },
"engines": { "directories": {
"node": "8.9.4", "doc": "docs"
"npm": "5.6.0" },
"repository": {
"type": "git",
"url": "git+https://github.com/Qolzam/react-social-network.git"
},
"keywords": [
"react",
"redux",
"saga",
"redux-saga",
"router",
"react-router",
"firebase",
"social",
"media",
"app",
"mobile"
],
"bugs": {
"url": "https://github.com/Qolzam/react-social-network/issues"
} }
} }

View File

@@ -12,7 +12,7 @@
body { body {
margin: 0; margin: 0;
background-color: #eeeeee; background-color: #fafafa;
height: 100% height: 100%
} }
@@ -106,7 +106,7 @@
padding: 0.01em 16px; padding: 0.01em 16px;
} }
</style> </style>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head> </head>
<body> <body>

View File

@@ -1,39 +0,0 @@
import { initialize } from 'react-localize-redux'
import { addTranslationForLanguage } from 'react-localize-redux'
import { setActiveLanguage } from 'react-localize-redux'
import { LanguageType } from 'reducers/locale/langugeType'
import config from 'src/config'
/**
* Initialize translation
*/
export const initTranslation = () => {
return (dispatch: Function , getState: Function) => {
// - Intialize language
const languages = [
{ name: 'English', code: LanguageType.English },
{ name: 'French', code: LanguageType.French },
{ name: 'Spanish', code: LanguageType.Spanish }
]
dispatch(initialize(languages))
// To set a different default active language set the `defaultLanguage` option.
dispatch(initialize(languages, { defaultLanguage: config.settings.defaultLanguage }))
const englishLocale = require('locale/en.json')
const spanishLocale = require('locale/es.json')
dispatch(addTranslationForLanguage(englishLocale, LanguageType.English))
dispatch(addTranslationForLanguage(spanishLocale, LanguageType.Spanish))
}
}
/**
* Set active language for translation
*/
export const setLanguage = (language: LanguageType) => {
return (dispatch: Function , getState: Function) => {
// Dispatch `setActiveLanguage` and pass the language.
dispatch(setActiveLanguage(language))
}
}

34
src/api/CommentAPI.ts Normal file
View File

@@ -0,0 +1,34 @@
import StringAPI from 'api/StringAPI'
import { ServerRequestType } from 'constants/serverRequestType'
import { ServerRequestModel } from 'models/server'
import { ServerRequestStatusType } from 'store/actions/serverRequestStatusType'
import { comments } from 'models/comments/commentTypes'
import * as _ from 'lodash'
/**
* Create get comments server request model
*/
const createGetCommentsRequest = (postId: string) => {
const requestId = StringAPI.createServerRequestId(ServerRequestType.CommentGetComments, postId)
return new ServerRequestModel(
ServerRequestType.CommentGetComments,
requestId,
'',
ServerRequestStatusType.Sent
)
}
const sortCommentsByDate = (sortedObjects: comments) => {
const commentKeys = Object.keys(sortedObjects)
if (commentKeys.length > 1) {
return _.fromPairs(_.toPairs(sortedObjects)
.sort((a: any, b: any) => parseInt(b[1].creationDate, 10) - parseInt(a[1].creationDate, 10)).slice(0, 3))
}
return sortedObjects
}
export default {
createGetCommentsRequest,
sortCommentsByDate
}

View File

@@ -4,11 +4,14 @@ import * as moment from 'moment/moment'
* @param title log title * @param title log title
* @param data log data object * @param data log data object
*/ */
const logger = (title: string, data: any) => { const logger = (title: string, ...data: any[]) => {
const randomColor = getRandomColor() const randomColor = getRandomColor()
console.log(`\n\n%c ======= ${title} ======= %c${moment().format('HH:mm:ss SSS')} \n`, `color:${randomColor};font-size:15` window['console']['log'](`\n\n%c ======= ${title} ======= %c${moment().format('HH:mm:ss SSS')} \n`, `color:${randomColor};font-size:15`
, `color:${getRandomColor()};font-size:15`, data,`\n\n =========================================`) , `color:${getRandomColor()};font-size:15`)
window['console']['log'](``)
window['console']['log'](` `, data)
window['console']['log'](`\n =========================================`)
} }
@@ -24,7 +27,18 @@ const getRandomColor = () => {
return color return color
} }
const updateObject = (oldObject: any, updatedProperties: any) => {
return {
...oldObject,
...updatedProperties
}
}
const getStateSlice = (state: any) => state.toJS()['locale']
export default { export default {
logger, logger,
getRandomColor getRandomColor,
updateObject,
getStateSlice
} }

View File

@@ -1,14 +1,3 @@
// - Interface declaration
interface FileReaderEventTarget extends EventTarget {
result: string
}
interface FileReaderEvent extends Event {
target: FileReaderEventTarget
getMessage (): string
}
// - Get file Extension // - Get file Extension
const getExtension = (fileName: string) => { const getExtension = (fileName: string) => {
let re: RegExp = /(?:\.([^.]+))?$/ let re: RegExp = /(?:\.([^.]+))?$/
@@ -36,9 +25,9 @@ const constraintImage = (file: File,fileName: string, maxWidth?: number, maxHeig
if (file.type.match(/image.*/)) { if (file.type.match(/image.*/)) {
// Load the image // Load the image
let reader = new FileReader() let reader = new FileReader()
reader.onload = function (readerEvent: FileReaderEvent) { reader.onload = (readerEvent: any) => {
let image = new Image() let image = new Image()
image.onload = function (imageEvent: Event) { image.onload = (imageEvent: Event) => {
// Resize the image // Resize the image
let canvas: HTMLCanvasElement = document.createElement('canvas') let canvas: HTMLCanvasElement = document.createElement('canvas')

View File

@@ -1,3 +1,4 @@
import {List} from 'immutable'
// Get tags from post content // Get tags from post content
export const detectTags: (content: string, character: string) => string[] = (content: string, character: string) => { export const detectTags: (content: string, character: string) => string[] = (content: string, character: string) => {
@@ -27,3 +28,15 @@ export const sortObjectsDate = (objects: any) => {
return sortedObjects return sortedObjects
} }
export const sortImuObjectsDate = (objects: List<Map<string, any>>) => {
let sortedObjects = objects
// Sort posts with creation date
return sortedObjects.sort((a: any, b: any) => {
return parseInt(b.get('creationDate'),10) - parseInt(a.get('creationDate'),10)
})
// return sortedObjects
}

View File

@@ -2,36 +2,38 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { push } from 'react-router-redux' import { push } from 'react-router-redux'
import List, { import {Map, List as ImuList} from 'immutable'
ListItem,
ListItemIcon, // - Material UI
ListItemSecondaryAction, import ListItemText from '@material-ui/core/ListItemText'
ListItemText import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
} from 'material-ui/List' import ListItem from '@material-ui/core/ListItem'
import SvgGroup from 'material-ui-icons/GroupWork' import List from '@material-ui/core/List'
import IconButton from 'material-ui/IconButton' import ListItemIcon from '@material-ui/core/ListItemIcon'
import MoreVertIcon from 'material-ui-icons/MoreVert' import SvgGroup from '@material-ui/icons/GroupWork'
import TextField from 'material-ui/TextField' import IconButton from '@material-ui/core/IconButton'
import { MenuList, MenuItem } from 'material-ui/Menu' import MoreVertIcon from '@material-ui/icons/MoreVert'
import { withStyles } from 'material-ui/styles' import TextField from '@material-ui/core/TextField'
import MenuList from '@material-ui/core/MenuList'
import MenuItem from '@material-ui/core/MenuItem'
import { withStyles } from '@material-ui/core/styles'
import { Manager, Target, Popper } from 'react-popper' import { Manager, Target, Popper } from 'react-popper'
import Grow from 'material-ui/transitions/Grow' import Grow from '@material-ui/core/Grow'
import ClickAwayListener from 'material-ui/utils/ClickAwayListener' import ClickAwayListener from '@material-ui/core/ClickAwayListener'
import classNames from 'classnames' import classNames from 'classnames'
import IconButtonElement from 'layouts/IconButtonElement' import IconButtonElement from 'layouts/IconButtonElement'
import Dialog, { import Dialog from '@material-ui/core/Dialog'
DialogActions, import DialogActions from '@material-ui/core/DialogActions'
DialogContent, import DialogContent from '@material-ui/core/DialogContent'
DialogContentText, import DialogTitle from '@material-ui/core/DialogTitle'
DialogTitle import DialogContentText from '@material-ui/core/DialogContentText'
} from 'material-ui/Dialog' import Divider from '@material-ui/core/Divider'
import Divider from 'material-ui/Divider' import Button from '@material-ui/core/Button'
import Button from 'material-ui/Button' import RaisedButton from '@material-ui/core/Button'
import RaisedButton from 'material-ui/Button' import SvgClose from '@material-ui/icons/Close'
import SvgClose from 'material-ui-icons/Close' import AppBar from '@material-ui/core/AppBar'
import AppBar from 'material-ui/AppBar' import Paper from '@material-ui/core/Paper'
import Paper from 'material-ui/Paper' import Collapse from '@material-ui/core/Collapse'
import Collapse from 'material-ui/transitions/Collapse'
// - Import app components // - Import app components
import UserAvatar from 'components/userAvatar' import UserAvatar from 'components/userAvatar'
@@ -39,7 +41,7 @@ import UserAvatar from 'components/userAvatar'
// - Import API // - Import API
// - Import actions // - Import actions
import * as circleActions from 'actions/circleActions' import * as circleActions from 'store/actions/circleActions'
import { ICircleComponentProps } from './ICircleComponentProps' import { ICircleComponentProps } from './ICircleComponentProps'
import { ICircleComponentState } from './ICircleComponentState' import { ICircleComponentState } from './ICircleComponentState'
@@ -61,6 +63,14 @@ const styles = (theme: any) => ({
}, },
dialogPaper: { dialogPaper: {
minWidth: 400 minWidth: 400
},
fullPageXs: {
[theme.breakpoints.down('xs')]: {
width: '100%',
height: '100%',
margin: 0,
overflowY: 'auto'
}
} }
}) })
@@ -111,7 +121,7 @@ export class CircleComponent extends Component<ICircleComponentProps, ICircleCom
/** /**
* Circle name on change * Circle name on change
*/ */
circleName: this.props.circle.name, circleName: this.props.circle.get('name', ''),
/** /**
* Save operation will be disable if user doesn't meet requirement * Save operation will be disable if user doesn't meet requirement
*/ */
@@ -202,9 +212,9 @@ export class CircleComponent extends Component<ICircleComponentProps, ICircleCom
let usersParsed: any = [] let usersParsed: any = []
if (usersOfCircle) { if (usersOfCircle) {
Object.keys(usersOfCircle).forEach((userId, index) => { usersOfCircle.forEach((user: Map<string, any>, userId) => {
const { fullName } = usersOfCircle[userId] const fullName = user.get('fullName')
let avatar = usersOfCircle && usersOfCircle[userId] ? usersOfCircle[userId].avatar || '' : '' let avatar = user.get('avatar', '')
usersParsed.push( usersParsed.push(
<ListItem <ListItem
button button
@@ -295,9 +305,9 @@ export class CircleComponent extends Component<ICircleComponentProps, ICircleCom
<ListItemIcon className={classes.icon}> <ListItemIcon className={classes.icon}>
<SvgGroup /> <SvgGroup />
</ListItemIcon> </ListItemIcon>
<ListItemText inset primary={<span style={this.styles}>{this.props.circle.name}</span>} /> <ListItemText inset primary={<span style={this.styles as any}>{this.props.circle.get('name')}</span>} />
<ListItemSecondaryAction> <ListItemSecondaryAction>
{circle.isSystem ? null : rightIconMenu} {circle.get('isSystem') ? null : rightIconMenu}
</ListItemSecondaryAction> </ListItemSecondaryAction>
</ListItem> </ListItem>
<Collapse component='li' in={this.state.open} timeout='auto' unmountOnExit> <Collapse component='li' in={this.state.open} timeout='auto' unmountOnExit>
@@ -306,6 +316,7 @@ export class CircleComponent extends Component<ICircleComponentProps, ICircleCom
</List> </List>
</Collapse> </Collapse>
<Dialog <Dialog
PaperProps={{className: classes.fullPageXs}}
key={this.props.id} key={this.props.id}
open={this.props.openSetting!} open={this.props.openSetting!}
onClose={this.props.closeCircleSettings} onClose={this.props.closeCircleSettings}
@@ -354,30 +365,27 @@ const mapDispatchToProps = (dispatch: any, ownProps: ICircleComponentProps) => {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any, ownProps: ICircleComponentProps) => { const mapStateToProps = (state: Map<string, any>, ownProps: ICircleComponentProps) => {
const { circle, authorize, server } = state const userTies: Map<string, any> = state.getIn(['circle', 'userTies'])
const { userTies } = circle const uid = state.getIn(['authorize', 'uid'])
const { uid } = state.authorize const circles: Map<string, Map<string, any>> = state.getIn(['circle', 'circleList'], {})
const circles: { [circleId: string]: Circle } = circle ? (circle.circleList || {}) : {} const currentCircle: Map<string, any> = circles.get(ownProps.id, Map({}))
const currentCircle = (circles ? circles[ownProps.id] : {}) as Circle const circleId = ownProps.circle.get('id')
const circleId = ownProps.circle.id! let usersOfCircle: Map<string, any> = Map({})
let usersOfCircle: { [userId: string]: UserTie } = {} userTies.forEach((userTie , userTieId) => {
Object.keys(userTies).forEach((userTieId) => { const theUserTie: Map<string, any> = userTie
const theUserTie = userTies[userTieId] as UserTie const circleList: ImuList<string> = theUserTie.getIn(['circleIdList'])
if (theUserTie.circleIdList!.indexOf(ownProps.id) > -1) { if ( circleList.indexOf(ownProps.id) > -1) {
usersOfCircle = { usersOfCircle = usersOfCircle.set(userTieId!, theUserTie)
...usersOfCircle,
[userTieId]: theUserTie
}
} }
}) })
return { return {
usersOfCircle, usersOfCircle,
openSetting: (state.circle && state.circle.openSetting && state.circle.openSetting[circleId]) ? state.circle.openSetting[circleId] : false, openSetting: state.getIn(['circle', 'openSetting', circleId], false),
userInfo: state.user.info userInfo: state.getIn(['user', 'info'])
} }
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(CircleComponent as any) as any) export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles as any)(CircleComponent as any) as any)

View File

@@ -1,81 +1,57 @@
import { Comment } from 'core/domain/comments' import { Comment } from 'core/domain/comments'
import { Profile } from 'core/domain/users' import { Profile } from 'core/domain/users'
import { Circle, UserTie } from 'core/domain/circles' import { Circle, UserTie } from 'core/domain/circles'
import {Map} from 'immutable'
export interface ICircleComponentProps { export interface ICircleComponentProps {
/** /**
* * Circle
*
* @type {Circle}
* @memberof ICircleComponentProps
*/ */
circle: Circle circle: Map<string, any>
/** /**
* Circle identifier * Circle identifier
*
* @type {string}
* @memberof ICircleComponentProps
*/ */
id: string id: string
/** /**
* User identifier * User identifier
*
* @type {string}
* @memberof ICircleComponentProps
*/ */
uid: string uid: string
/** /**
* * Update circle
*
* @type {Function}
* @memberof ICircleComponentProps
*/ */
updateCircle?: Function updateCircle?: Function
/** /**
* * Delete circle
*
* @type {Function}
* @memberof ICircleComponentProps
*/ */
deleteCircle?: Function deleteCircle?: Function
/** /**
* Users of current circle * Users of current circle
*/ */
usersOfCircle?: {[userId: string]: UserTie} usersOfCircle?: Map<string, any>
/** /**
* Close setting box of circle * Close setting box of circle
*
* @type {Function}
* @memberof ICircleComponentProps
*/ */
closeCircleSettings?: any closeCircleSettings?: any
/** /**
* Circle setting dialog is open {true} or not {false} * Circle setting dialog is open {true} or not {false}
*
* @type {Function}
* @memberof ICircleComponentProps
*/ */
openSetting?: boolean openSetting?: boolean
/** /**
* Change route location * Change route location
*
* @memberof ICircleComponentProps
*/ */
goTo?: (url: string) => void goTo?: (url: string) => void
/** /**
* Open setting box for a circle * Open setting box for a circle
*
* @memberof ICircleComponentProps
*/ */
openCircleSettings?: () => any openCircleSettings?: () => any

View File

@@ -1,29 +1,37 @@
// - Import react components // - Import react components
import React, { Component } from 'react' import React, { Component } from 'react'
import { findDOMNode } from 'react-dom'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { NavLink } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import moment from 'moment/moment' import moment from 'moment/moment'
import Linkify from 'react-linkify' import Linkify from 'react-linkify'
import Popover from '@material-ui/core/Popover'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import {Map} from 'immutable'
import { Comment } from 'core/domain/comments' import { Comment } from 'core/domain/comments'
// - Import material UI libraries // - Import material UI libraries
import Divider from 'material-ui/Divider' import Divider from '@material-ui/core/Divider'
import Paper from 'material-ui/Paper' import Paper from '@material-ui/core/Paper'
import Button from 'material-ui/Button' import Button from '@material-ui/core/Button'
import { grey } from 'material-ui/colors' import { grey } from '@material-ui/core/colors'
import IconButton from 'material-ui/IconButton' import IconButton from '@material-ui/core/IconButton'
import MoreVertIcon from 'material-ui-icons/MoreVert' import MoreVertIcon from '@material-ui/icons/MoreVert'
import List, { ListItem, ListItemIcon, ListItemText } from 'material-ui/List' import ListItemText from '@material-ui/core/ListItemText'
import Menu, { MenuList, MenuItem } from 'material-ui/Menu' import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
import TextField from 'material-ui/TextField' import ListItem from '@material-ui/core/ListItem'
import { withStyles } from 'material-ui/styles' import List from '@material-ui/core/List'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import MenuList from '@material-ui/core/MenuList'
import MenuItem from '@material-ui/core/MenuItem'
import TextField from '@material-ui/core/TextField'
import { withStyles } from '@material-ui/core/styles'
import { Manager, Target, Popper } from 'react-popper' import { Manager, Target, Popper } from 'react-popper'
import { Card, CardActions, CardHeader, CardMedia, CardContent } from 'material-ui' import { Card, CardActions, CardHeader, CardMedia, CardContent } from '@material-ui/core'
import Grow from 'material-ui/transitions/Grow' import Grow from '@material-ui/core/Grow'
import ClickAwayListener from 'material-ui/utils/ClickAwayListener' import ClickAwayListener from '@material-ui/core/ClickAwayListener'
import classNames from 'classnames' import classNames from 'classnames'
// - Import app components // - Import app components
@@ -32,8 +40,8 @@ import UserAvatar from 'components/userAvatar'
// - Import API // - Import API
// - Import actions // - Import actions
import * as commentActions from 'actions/commentActions' import * as commentActions from 'store/actions/commentActions'
import * as userActions from 'actions/userActions' import * as userActions from 'store/actions/userActions'
import { ICommentComponentProps } from './ICommentComponentProps' import { ICommentComponentProps } from './ICommentComponentProps'
import { ICommentComponentState } from './ICommentComponentState' import { ICommentComponentState } from './ICommentComponentState'
@@ -90,7 +98,6 @@ const styles = (theme: any) => ({
* Create component class * Create component class
*/ */
export class CommentComponent extends Component<ICommentComponentProps, ICommentComponentState> { export class CommentComponent extends Component<ICommentComponentProps, ICommentComponentState> {
static propTypes = { static propTypes = {
/** /**
* Comment object * Comment object
@@ -106,6 +113,11 @@ export class CommentComponent extends Component<ICommentComponentProps, IComment
disableComments: PropTypes.bool disableComments: PropTypes.bool
} }
/**
* Fields
*/
buttonMenu = null
/** /**
* DOM styles * DOM styles
* *
@@ -181,7 +193,11 @@ export class CommentComponent extends Component<ICommentComponentProps, IComment
/** /**
* The anchor of comment menu element * The anchor of comment menu element
*/ */
openMenu: false openMenu: false,
/**
* Anchor element
*/
anchorEl: null
} }
@@ -221,7 +237,6 @@ export class CommentComponent extends Component<ICommentComponentProps, IComment
*/ */
handleUpdateComment = (evt: any) => { handleUpdateComment = (evt: any) => {
const { comment } = this.props const { comment } = this.props
comment.editorStatus = undefined
comment.text = this.state.text comment.text = this.state.text
this.props.update!(comment) this.props.update!(comment)
this.setState({ this.setState({
@@ -265,7 +280,7 @@ export class CommentComponent extends Component<ICommentComponentProps, IComment
* Handle comment menu * Handle comment menu
*/ */
handleCommentMenu = (event: any) => { handleCommentMenu = (event: any) => {
this.setState({ openMenu: true }) this.setState({ openMenu: true, anchorEl: findDOMNode(this.buttonMenu!), })
} }
/** /**
@@ -276,8 +291,8 @@ export class CommentComponent extends Component<ICommentComponentProps, IComment
} }
componentWillMount () { componentWillMount () {
const { userId } = this.props.comment const { commentOwner } = this.props
if (!this.props.isCommentOwner && !this.props.info![userId!]) { if (!this.props.isCommentOwner && !commentOwner) {
this.props.getUserInfo!() this.props.getUserInfo!()
} }
} }
@@ -291,30 +306,44 @@ export class CommentComponent extends Component<ICommentComponentProps, IComment
/** /**
* Comment object from props * Comment object from props
*/ */
const { comment, classes, fullName, avatar, translate } = this.props const { comment, classes, fullName, avatar, translate , editorStatus} = this.props
const { openMenu } = this.state const { openMenu, anchorEl } = this.state
const RightIconMenu = () => ( const rightIconMenu = (
<Manager <>
className={classes.iconButton}
>
<Target>
<IconButton <IconButton
buttonRef={(node: any) => {
this.buttonMenu = node
}}
aria-owns={openMenu! ? 'comment-menu' : ''} aria-owns={openMenu! ? 'comment-menu' : ''}
aria-haspopup='true' aria-haspopup='true'
onClick={this.handleCommentMenu} onClick={this.handleCommentMenu}
> >
<MoreVertIcon className={classes.moreIcon} /> <MoreVertIcon className={classes.moreIcon} />
</IconButton> </IconButton>
</Target> {/* <Popper
<Popper
placement='bottom-start' placement='bottom-start'
eventsEnabled={openMenu!} eventsEnabled={openMenu!}
className={classNames({ [classes.popperClose]: !openMenu! }, { [classes.popperOpen]: openMenu! })} className={classNames({ [classes.popperClose]: !openMenu! }, { [classes.popperOpen]: openMenu! })}
> >
<ClickAwayListener onClickAway={this.handleCloseCommentMenu}> <ClickAwayListener onClickAway={this.handleCloseCommentMenu}>
<Grow in={openMenu!} > <Grow in={openMenu!} > */}
<Popover
open={openMenu!}
anchorEl={anchorEl}
anchorReference={'anchorEl'}
anchorPosition={{ top: 0, left: 0 }}
onClose={this.handleCloseCommentMenu}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
transformOrigin={{
vertical: 'bottom',
horizontal: 'center',
}}
>
<Paper> <Paper>
<MenuList role='menu'> <MenuList role='menu'>
<MenuItem className={classes.rightIconMenuItem}>{translate!('comment.replyButton')}</MenuItem> <MenuItem className={classes.rightIconMenuItem}>{translate!('comment.replyButton')}</MenuItem>
@@ -322,10 +351,9 @@ export class CommentComponent extends Component<ICommentComponentProps, IComment
{(this.props.isCommentOwner || this.props.isPostOwner) ? (<MenuItem className={classes.rightIconMenuItem} onClick={(evt: any) => this.handleDelete(evt, comment.id, comment.postId)}>{translate!('comment.deleteButton')}</MenuItem>) : ''} {(this.props.isCommentOwner || this.props.isPostOwner) ? (<MenuItem className={classes.rightIconMenuItem} onClick={(evt: any) => this.handleDelete(evt, comment.id, comment.postId)}>{translate!('comment.deleteButton')}</MenuItem>) : ''}
</MenuList> </MenuList>
</Paper> </Paper>
</Grow> </Popover>
</ClickAwayListener>
</Popper> </>
</Manager>
) )
const Author = () => ( const Author = () => (
@@ -336,7 +364,7 @@ export class CommentComponent extends Component<ICommentComponentProps, IComment
}}>{moment.unix(comment.creationDate!).fromNow()}</span> }}>{moment.unix(comment.creationDate!).fromNow()}</span>
</div> </div>
) )
const { userId, editorStatus } = comment const { userId } = comment
const commentBody = ( const commentBody = (
<div style={{ outline: 'none', flex: 'auto', flexGrow: 1 }}> <div style={{ outline: 'none', flex: 'auto', flexGrow: 1 }}>
{ editorStatus ? <TextField { editorStatus ? <TextField
@@ -373,7 +401,7 @@ export class CommentComponent extends Component<ICommentComponentProps, IComment
title={editorStatus ? '' : <Author />} title={editorStatus ? '' : <Author />}
subheader={commentBody} subheader={commentBody}
avatar={<NavLink to={`/${userId}`}><UserAvatar fullName={fullName!} fileName={avatar!} size={24} /></NavLink>} avatar={<NavLink to={`/${userId}`}><UserAvatar fullName={fullName!} fileName={avatar!} size={24} /></NavLink>}
action={(!this.props.isCommentOwner && !this.props.isPostOwner && this.props.disableComments) || editorStatus ? '' : (<RightIconMenu />)} action={(!this.props.isCommentOwner && !this.props.isPostOwner && this.props.disableComments) || editorStatus ? '' : rightIconMenu}
> >
</CardHeader> </CardHeader>
@@ -410,15 +438,16 @@ const mapDispatchToProps = (dispatch: any, ownProps: ICommentComponentProps) =>
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any, ownProps: any) => { const mapStateToProps = (state: any, ownProps: ICommentComponentProps) => {
const { uid } = state.authorize const commentOwnerId = ownProps.comment.userId
const avatar = state.user.info && state.user.info[ownProps.comment.userId] ? state.user.info[ownProps.comment.userId].avatar || '' : '' const uid = state.getIn(['authorize', 'uid'])
const fullName = state.user.info && state.user.info[ownProps.comment.userId] ? state.user.info[ownProps.comment.userId].fullName || '' : '' const avatar = ownProps.comment.userAvatar
const fullName = ownProps.comment.userDisplayName
return { return {
translate: getTranslate(state.locale), translate: getTranslate(state.get('locale')),
uid: uid, uid: uid,
isCommentOwner: (uid === ownProps.comment.userId), isCommentOwner: (uid === commentOwnerId),
info: state.user.info, commentOwner: state.getIn(['user', 'info', commentOwnerId]),
avatar, avatar,
fullName fullName
} }

View File

@@ -10,6 +10,11 @@ export interface ICommentComponentProps {
*/ */
comment: Comment comment: Comment
/**
* Comment owner
*/
commentOwner?: Profile
/** /**
* Open profile editor * Open profile editor
* *
@@ -63,14 +68,6 @@ export interface ICommentComponentProps {
*/ */
getUserInfo?: () => void getUserInfo?: () => void
/**
* User profile
*
* @type {{[userId: string]: Profile}}
* @memberof ICommentComponentProps
*/
info?: {[userId: string]: Profile}
/** /**
* User full name * User full name
* *
@@ -95,6 +92,11 @@ export interface ICommentComponentProps {
*/ */
disableComments?: boolean disableComments?: boolean
/**
* Whether comment edit is open
*/
editorStatus: boolean
/** /**
* Styles * Styles
*/ */

View File

@@ -45,4 +45,9 @@ export interface ICommentComponentState {
* Wheter comment menu is open * Wheter comment menu is open
*/ */
openMenu?: boolean openMenu?: boolean
/**
* Anchor element
*/
anchorEl: any
} }

View File

@@ -6,23 +6,28 @@ import { NavLink } from 'react-router-dom'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import moment from 'moment/moment' import moment from 'moment/moment'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import { Map } from 'immutable'
import Paper from 'material-ui/Paper' import Paper from '@material-ui/core/Paper'
import Button from 'material-ui/Button' import Button from '@material-ui/core/Button'
import TextField from 'material-ui/TextField' import TextField from '@material-ui/core/TextField'
import Divider from 'material-ui/Divider' import Divider from '@material-ui/core/Divider'
import { ListItemIcon, ListItemText, ListItem } from 'material-ui/List' import ListItemText from '@material-ui/core/ListItemText'
import { grey, teal } from 'material-ui/colors' import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
import { LinearProgress } from 'material-ui/Progress' import ListItem from '@material-ui/core/ListItem'
import { withStyles } from 'material-ui/styles' import List from '@material-ui/core/List'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import { grey, teal } from '@material-ui/core/colors'
import LinearProgress from '@material-ui/core/LinearProgress'
import { withStyles } from '@material-ui/core/styles'
import { Manager, Target, Popper } from 'react-popper' import { Manager, Target, Popper } from 'react-popper'
import { Card, CardActions, CardHeader, CardMedia, CardContent } from 'material-ui' import { Card, CardActions, CardHeader, CardMedia, CardContent } from '@material-ui/core'
import Grow from 'material-ui/transitions/Grow' import Grow from '@material-ui/core/Grow'
import ClickAwayListener from 'material-ui/utils/ClickAwayListener' import ClickAwayListener from '@material-ui/core/ClickAwayListener'
import classNames from 'classnames' import classNames from 'classnames'
// - Import actions // - Import actions
import * as commentActions from 'actions/commentActions' import * as commentActions from 'store/actions/commentActions'
// - Import app components // - Import app components
import CommentListComponent from 'components/commentList' import CommentListComponent from 'components/commentList'
@@ -30,11 +35,12 @@ import UserAvatar from 'components/userAvatar'
import { ICommentGroupComponentProps } from './ICommentGroupComponentProps' import { ICommentGroupComponentProps } from './ICommentGroupComponentProps'
import { ICommentGroupComponentState } from './ICommentGroupComponentState' import { ICommentGroupComponentState } from './ICommentGroupComponentState'
import { Comment } from 'core/domain/comments/comment' import { Comment } from 'core/domain/comments'
import { ServerRequestModel } from 'models/server' import { ServerRequestModel } from 'models/server'
import StringAPI from 'api/StringAPI' import StringAPI from 'api/StringAPI'
import { ServerRequestType } from 'constants/serverRequestType' import { ServerRequestType } from 'constants/serverRequestType'
import { ServerRequestStatusType } from 'actions/serverRequestStatusType' import { ServerRequestStatusType } from 'store/actions/serverRequestStatusType'
import { Profile } from 'core/domain/users'
const styles = (theme: any) => ({ const styles = (theme: any) => ({
textField: { textField: {
@@ -210,8 +216,8 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
* @return {DOM} list of comments' DOM * @return {DOM} list of comments' DOM
*/ */
commentList = () => { commentList = () => {
const {classes} = this.props const {classes, postId} = this.props
let comments = this.props.commentSlides let comments = Map(this.props.commentSlides!).toJS()
if (comments) { if (comments) {
comments = _.fromPairs(_.toPairs(comments) comments = _.fromPairs(_.toPairs(comments)
.sort((a: any, b: any) => parseInt(b[1].creationDate, 10) - parseInt(a[1].creationDate, 10))) .sort((a: any, b: any) => parseInt(b[1].creationDate, 10) - parseInt(a[1].creationDate, 10)))
@@ -229,10 +235,8 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
parsedComments.push(parsedComments[0]) parsedComments.push(parsedComments[0])
} }
return parsedComments.map((comment, index) => { return parsedComments.map((comment, index) => {
const { userInfo } = this.props const commentAvatar = comment.userAvatar
const commentFullName = comment.userDisplayName
const commentAvatar = userInfo && userInfo[comment.userId!] ? userInfo[comment.userId!].avatar || '' : ''
const commentFullName = userInfo && userInfo[comment.userId!] ? userInfo[comment.userId!].fullName || '' : ''
const commentBody = ( const commentBody = (
<div style={{ outline: 'none', flex: 'auto', flexGrow: 1 }}> <div style={{ outline: 'none', flex: 'auto', flexGrow: 1 }}>
@@ -274,8 +278,8 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render () { render () {
const { comments, classes, postId, fullName, avatar, getCommentsRequest, open, commentSlides, translate } = this.props const { classes, postId, fullName, avatar, commentsRequestStatus, open, commentSlides, translate } = this.props
const comments: Map<string, Comment> = this.props.comments || Map({})
/** /**
* Comment list box * Comment list box
*/ */
@@ -314,20 +318,20 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
</div> </div>
) )
const showComments = ( comments && Object.keys(comments).length > 0 const showComments = ( !comments.isEmpty()
? ( ? (
<Paper elevation={0} style={open ? { display: 'block', padding: '0px 0px' } : { display: 'none', padding: '12px 16px' }}> <Paper elevation={0} style={open ? { display: 'block', padding: '0px 0px' } : { display: 'none', padding: '12px 16px' }}>
<CommentListComponent comments={comments!} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments} /> <CommentListComponent comments={comments!} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments} postId={postId}/>
</Paper>) </Paper>)
: '') : '')
const loadComments = (( getCommentsRequest === undefined || (getCommentsRequest && getCommentsRequest!.status !== ServerRequestStatusType.OK)) ? <LinearProgress style={this.styles.progressbar} variant='indeterminate' /> : showComments) const loadComments = ((commentsRequestStatus === ServerRequestStatusType.OK) || !comments.isEmpty() ? showComments : <LinearProgress style={this.styles.progressbar} variant='indeterminate' />)
/** /**
* Return Elements * Return Elements
*/ */
return ( return (
<div key={postId + '-comments'}> <div key={postId + '-comments-group'}>
<div style={commentSlides && Object.keys(commentSlides).length > 0 ? { display: 'block' } : { display: 'none' }}>
<Divider /> <Divider />
<div style={commentSlides && !commentSlides.isEmpty() ? { display: 'block' } : { display: 'none' }}>
<Paper elevation={0} className='animate-top' style={!open ? { display: 'block' } : { display: 'none' }}> <Paper elevation={0} className='animate-top' style={!open ? { display: 'block' } : { display: 'none' }}>
<div style={{ position: 'relative', height: '60px' }} > <div style={{ position: 'relative', height: '60px' }} >
@@ -339,11 +343,11 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
</div> </div>
</div> </div>
</Paper> </Paper>
</div>
{ {
open ? loadComments : '' open ? loadComments : ''
} }
</div>
{ {
(!this.props.disableComments && open ) (!this.props.disableComments && open )
? commentWriteBox ? commentWriteBox
@@ -377,19 +381,20 @@ const mapDispatchToProps = (dispatch: any, ownProps: ICommentGroupComponentProps
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any, ownProps: ICommentGroupComponentProps) => { const mapStateToProps = (state: Map<string, any>, ownProps: ICommentGroupComponentProps) => {
const { post, user, authorize, server } = state
const {request} = server
const { ownerPostUserId, postId } = ownProps const { ownerPostUserId, postId } = ownProps
const commentSlides = post.userPosts[ownerPostUserId] && post.userPosts[ownerPostUserId][postId] ? post.userPosts[ownerPostUserId][postId].comments : {} const uid = state.getIn(['authorize', 'uid'], 0)
const getCommentsRequest: ServerRequestModel = request ? request[StringAPI.createServerRequestId(ServerRequestType.CommentGetComments, postId)] : null const requestId = StringAPI.createServerRequestId(ServerRequestType.CommentGetComments, postId)
const commentsRequestStatus = state.getIn(['server', 'request', requestId])
const commentSlides = state.getIn(['post', 'userPosts', ownerPostUserId, postId, 'comments'])
const user = state.getIn(['user', 'info', uid])
return { return {
translate: getTranslate(state.locale), translate: getTranslate(state.get('locale')),
getCommentsRequest, commentsRequestStatus : commentsRequestStatus ? commentsRequestStatus.status : ServerRequestStatusType.NoAction,
commentSlides, commentSlides,
avatar: user.info && user.info[state.authorize.uid] ? user.info[authorize.uid].avatar || '' : '', avatar: user.avatar || '',
fullName: user.info && user.info[state.authorize.uid] ? user.info[authorize.uid].fullName || '' : '', fullName: user.fullName || '',
userInfo: user.info userInfo: state.getIn(['user', 'info'])
} }
} }

View File

@@ -1,6 +1,8 @@
import { Profile } from 'core/domain/users' import { Profile } from 'core/domain/users'
import { Comment } from 'core/domain/comments' import { Comment } from 'core/domain/comments'
import { ServerRequestModel } from 'models/server' import { ServerRequestModel } from 'models/server'
import { ServerRequestStatusType } from 'store/actions/serverRequestStatusType'
import {Map} from 'immutable'
export interface ICommentGroupComponentProps { export interface ICommentGroupComponentProps {
/** /**
@@ -9,15 +11,12 @@ export interface ICommentGroupComponentProps {
* @type {{[commentId: string]: Comment}} * @type {{[commentId: string]: Comment}}
* @memberof ICommentGroupComponentProps * @memberof ICommentGroupComponentProps
*/ */
comments?: {[commentId: string]: Comment} comments?: Map<string, Comment>
/** /**
* Commnets show on slide preview * Commnets show on slide preview
*
* @type {{[commentId: string]: Comment}}
* @memberof ICommentGroupComponentProps
*/ */
commentSlides?: {[commentId: string]: Comment} commentSlides?: Map<string, Comment>
/** /**
* The post identifier which comment belong to * The post identifier which comment belong to
@@ -33,7 +32,7 @@ export interface ICommentGroupComponentProps {
* @type {{[userId: string]: Profile}} * @type {{[userId: string]: Profile}}
* @memberof ICommentGroupComponentProps * @memberof ICommentGroupComponentProps
*/ */
userInfo?: {[userId: string]: Profile} userInfo?: Map<string, Profile>
/** /**
* Comment group is open {true} or not {false} * Comment group is open {true} or not {false}
@@ -102,7 +101,7 @@ export interface ICommentGroupComponentProps {
/** /**
* Get post comments request payload * Get post comments request payload
*/ */
getCommentsRequest?: ServerRequestModel commentsRequestStatus?: ServerRequestStatusType
/** /**
* Styles * Styles

View File

@@ -2,7 +2,16 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import List, { ListItem, ListItemText } from 'material-ui/List' import {Map} from 'immutable'
// - Material UI
import { withStyles } from '@material-ui/core/styles'
import ListItemText from '@material-ui/core/ListItemText'
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
import ListItem from '@material-ui/core/ListItem'
import List from '@material-ui/core/List'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemAvatar from '@material-ui/core/ListItemAvatar'
// - Import app components // - Import app components
import CommentComponent from 'components/comment' import CommentComponent from 'components/comment'
@@ -15,6 +24,15 @@ import { Comment } from 'core/domain/comments'
// - Import actions // - Import actions
const styles = (theme: any) => ({
list: {
width: '100%',
maxHeight: 290,
overflowY: 'auto',
overflowX: 'visible'
}
})
/** /**
* Create component class * Create component class
*/ */
@@ -54,21 +72,30 @@ export class CommentListComponent extends Component<ICommentListComponentProps,
* @return {DOM} list of comments' DOM * @return {DOM} list of comments' DOM
*/ */
commentList = () => { commentList = () => {
let comments = this.props.comments let comments = Map<string, Comment>(this.props.comments)
if (comments) { let commentsEditorStatus = Map<string, boolean>(this.props.commentsEditorStatus!)
if (!comments.isEmpty()) {
let parsedComments: Comment[] = [] let parsedComments: Comment[] = []
Object.keys(comments).forEach((commentId) => { comments.forEach((comment, commentId) => {
parsedComments.push({ parsedComments.push({
id: commentId, id: commentId,
...comments[commentId] ...Map(comment!).toJS()
}) })
}) })
let sortedComments = PostAPI.sortObjectsDate(parsedComments) let sortedComments = PostAPI.sortObjectsDate(parsedComments)
return sortedComments.map((comment: Comment, index: number, array: Comment) => { return sortedComments.map((comment: Comment, index: number, array: Comment) => {
return <CommentComponent key={comment.id!} comment={comment} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/> return (
<CommentComponent
key={comment.id!}
comment={comment}
isPostOwner={this.props.isPostOwner}
disableComments={this.props.disableComments}
editorStatus={(commentsEditorStatus.get(comment.id!, false))}
/>
)
}) })
@@ -80,17 +107,11 @@ export class CommentListComponent extends Component<ICommentListComponentProps,
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render () { render () {
const {classes, postId} = this.props
const styles: any = {
list: {
width: '100%',
maxHeight: 450
}
}
return ( return (
<List style={styles.list}> <List key={`comment-list-${postId}`} className={classes.list}>
{this.commentList()} {this.commentList()}
</List> </List>
@@ -116,11 +137,12 @@ const mapDispatchToProps = (dispatch: any, ownProps: ICommentListComponentProps)
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any) => { const mapStateToProps = (state: Map<string, any>, ownProps: ICommentListComponentProps) => {
const commentsEditorStatus = state.getIn(['comment', 'editorStatus', ownProps.postId ], {})
return { return {
commentsEditorStatus
} }
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(CommentListComponent as any) export default connect(mapStateToProps, mapDispatchToProps)((withStyles(styles as any)(CommentListComponent as any))as any)

View File

@@ -1,4 +1,5 @@
import { Comment } from 'core/domain/comments' import { Comment } from 'core/domain/comments'
import {Map} from 'immutable'
export interface ICommentListComponentProps { export interface ICommentListComponentProps {
@@ -8,7 +9,12 @@ export interface ICommentListComponentProps {
* @type {{[commentId: string]: Comment}} * @type {{[commentId: string]: Comment}}
* @memberof ICommentListComponentProps * @memberof ICommentListComponentProps
*/ */
comments: {[commentId: string]: Comment} comments: Map<string, Comment>
/**
* Comments editor status
*/
commentsEditorStatus?: {[commentId: string]: boolean}
/** /**
* Current user is post the post owner {true} or not false * Current user is post the post owner {true} or not false
@@ -18,6 +24,11 @@ export interface ICommentListComponentProps {
*/ */
isPostOwner: boolean isPostOwner: boolean
/**
* The post identifier comments belong to
*/
postId: string
/** /**
* Comment on the post is disabled {false} or not {true} * Comment on the post is disabled {false} or not {true}
* *
@@ -25,4 +36,9 @@ export interface ICommentListComponentProps {
* @memberof ICommentListComponentProps * @memberof ICommentListComponentProps
*/ */
disableComments: boolean disableComments: boolean
/**
* Styles
*/
classes?: any
} }

View File

@@ -3,42 +3,56 @@ import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import moment from 'moment/moment'
import DayPickerInput from 'react-day-picker/DayPickerInput'
import MomentLocaleUtils, {
formatDate,
parseDate,
} from 'react-day-picker/moment'
import {Map} from 'immutable'
import { grey } from 'material-ui/colors' import { grey } from '@material-ui/core/colors'
import IconButton from 'material-ui/IconButton' import IconButton from '@material-ui/core/IconButton'
import MoreVertIcon from 'material-ui-icons/MoreVert' import MoreVertIcon from '@material-ui/icons/MoreVert'
import SvgCamera from 'material-ui-icons/PhotoCamera' import SvgCamera from '@material-ui/icons/PhotoCamera'
import List, { ListItem, ListItemIcon, ListItemText } from 'material-ui/List' import ListItemText from '@material-ui/core/ListItemText'
import Menu, { MenuList, MenuItem } from 'material-ui/Menu' import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
import Button from 'material-ui/Button' import ListItem from '@material-ui/core/ListItem'
import RaisedButton from 'material-ui/Button' import List from '@material-ui/core/List'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import MenuList from '@material-ui/core/MenuList'
import MenuItem from '@material-ui/core/MenuItem'
import Button from '@material-ui/core/Button'
import RaisedButton from '@material-ui/core/Button'
import EventListener, { withOptions } from 'react-event-listener' import EventListener, { withOptions } from 'react-event-listener'
import Dialog, { import Dialog from '@material-ui/core/Dialog'
DialogActions, import DialogActions from '@material-ui/core/DialogActions'
DialogContent, import DialogContent from '@material-ui/core/DialogContent'
DialogContentText, import DialogTitle from '@material-ui/core/DialogTitle'
DialogTitle import DialogContentText from '@material-ui/core/DialogContentText'
} from 'material-ui/Dialog' import Divider from '@material-ui/core/Divider'
import Divider from 'material-ui/Divider' import Paper from '@material-ui/core/Paper'
import Paper from 'material-ui/Paper' import TextField from '@material-ui/core/TextField'
import TextField from 'material-ui/TextField' import Input from '@material-ui/core/Input'
import Input, { InputLabel } from 'material-ui/Input' import InputLabel from '@material-ui/core/InputLabel'
import { FormControl, FormHelperText } from 'material-ui/Form' import FormHelperText from '@material-ui/core/FormHelperText'
import { withStyles } from 'material-ui/styles' import FormControl from '@material-ui/core/FormControl'
import { withStyles } from '@material-ui/core/styles'
// - Import app components // - Import app components
import ImgCover from 'components/imgCover' import ImgCover from 'components/imgCover'
import UserAvatarComponent from 'components/userAvatar' import UserAvatarComponent from 'components/userAvatar'
import ImageGallery from 'components/imageGallery' import ImageGallery from 'components/imageGallery'
import AppDialogTitle from 'layouts/dialogTitle' import AppDialogTitle from 'layouts/dialogTitle'
import AppInput from 'layouts/appInput'
// - Import API // - Import API
import FileAPI from 'api/FileAPI' import FileAPI from 'api/FileAPI'
// - Import actions // - Import actions
import * as userActions from 'actions/userActions' import * as userActions from 'store/actions/userActions'
import * as globalActions from 'actions/globalActions' import * as globalActions from 'store/actions/globalActions'
import * as imageGalleryActions from 'actions/imageGalleryActions' import * as imageGalleryActions from 'store/actions/imageGalleryActions'
import { IEditProfileComponentProps } from './IEditProfileComponentProps' import { IEditProfileComponentProps } from './IEditProfileComponentProps'
import { IEditProfileComponentState } from './IEditProfileComponentState' import { IEditProfileComponentState } from './IEditProfileComponentState'
@@ -47,6 +61,48 @@ import { Profile } from 'core/domain/users'
const styles = (theme: any) => ({ const styles = (theme: any) => ({
dialogTitle: { dialogTitle: {
padding: 0 padding: 0
},
dialogContentRoot: {
maxHeight: 400,
minWidth: 330,
[theme.breakpoints.down('xs')]: {
maxHeight: '100%',
}
},
fullPageXs: {
[theme.breakpoints.down('xs')]: {
width: '100%',
height: '100%',
margin: 0
}
},
fixedDownStickyXS: {
[theme.breakpoints.down('xs')]: {
position: 'fixed',
bottom: 0,
right: 0,
background: 'white',
width: '100%'
}
},
bottomPaperSpace: {
height: 16,
[theme.breakpoints.down('xs')]: {
height: 90
}
},
box: {
padding: '0px 24px 0px',
display: 'flex'
},
bottomTextSpace: {
marginBottom: 15
},
dayPicker: {
width: '100%',
padding: '13px 0px 8px'
} }
}) })
@@ -100,11 +156,6 @@ export class EditProfileComponent extends Component<IEditProfileComponentProps,
updateButton: { updateButton: {
marginLeft: '10px' marginLeft: '10px'
}, },
box: {
padding: '0px 24px 20px 24px',
display: 'flex'
},
dialogGallery: { dialogGallery: {
width: '', width: '',
maxWidth: '530px', maxWidth: '530px',
@@ -156,7 +207,27 @@ export class EditProfileComponent extends Component<IEditProfileComponentProps,
/** /**
* It's true if the image gallery for avatar is open * It's true if the image gallery for avatar is open
*/ */
openAvatar: false openAvatar: false,
/**
* Default birth day
*/
defaultBirthday: (props.info && props.info.birthday) ? moment.unix(props.info!.birthday!).toDate() : '',
/**
* Seleted birth day
*/
selectedBirthday: 0,
/**
* Web URL
*/
webUrl: (props.info && props.info.webUrl) ? props.info.webUrl : '',
/**
* User company name
*/
companyName: (props.info && props.info.companyName) ? props.info.companyName : '',
/**
* User twitter id
*/
twitterId: (props.info && props.info.twitterId) ? props.info.twitterId : ''
} }
@@ -228,9 +299,10 @@ export class EditProfileComponent extends Component<IEditProfileComponentProps,
* @memberof EditProfile * @memberof EditProfile
*/ */
handleUpdate = () => { handleUpdate = () => {
const { fullNameInput, tagLineInput, avatar, banner } = this.state const { fullNameInput, tagLineInput, avatar, banner, selectedBirthday, companyName, webUrl, twitterId } = this.state
const { info, update } = this.props
if (this.state.fullNameInput.trim() === '') { if (fullNameInput.trim() === '') {
this.setState({ this.setState({
fullNameInputError: 'This field is required' fullNameInputError: 'This field is required'
}) })
@@ -239,12 +311,16 @@ export class EditProfileComponent extends Component<IEditProfileComponentProps,
fullNameInputError: '' fullNameInputError: ''
}) })
this.props.update!({ update!({
fullName: fullNameInput, fullName: fullNameInput,
tagLine: tagLineInput, tagLine: tagLineInput,
avatar: avatar, avatar: avatar,
banner: banner, banner: banner,
creationDate: this.props.info!.creationDate companyName: companyName,
webUrl: webUrl,
twitterId: twitterId,
creationDate: this.props.info!.creationDate,
birthday: selectedBirthday > 0 ? selectedBirthday : ((info && info.birthday) ? info!.birthday! : 0)
}) })
} }
} }
@@ -283,6 +359,13 @@ export class EditProfileComponent extends Component<IEditProfileComponentProps,
} }
} }
/**
* Handle birthday date changed
*/
handleBirthdayDateChange = (date: any) => {
this.setState({ selectedBirthday: moment(date).unix() })
}
componentDidMount() { componentDidMount() {
this.handleResize(null) this.handleResize(null)
} }
@@ -293,7 +376,8 @@ export class EditProfileComponent extends Component<IEditProfileComponentProps,
*/ */
render() { render() {
const { classes, translate } = this.props const { classes, translate, currentLanguage } = this.props
const { defaultBirthday, webUrl, twitterId, companyName } = this.state
const iconButtonElement = ( const iconButtonElement = (
<IconButton style={this.state.isSmall ? this.styles.iconButtonSmall : this.styles.iconButton}> <IconButton style={this.state.isSmall ? this.styles.iconButtonSmall : this.styles.iconButton}>
<MoreVertIcon style={{ ...(this.state.isSmall ? this.styles.iconButtonSmall : this.styles.iconButton), color: grey[400] }} viewBox='10 0 24 24' /> <MoreVertIcon style={{ ...(this.state.isSmall ? this.styles.iconButtonSmall : this.styles.iconButton), color: grey[400] }} viewBox='10 0 24 24' />
@@ -314,12 +398,13 @@ export class EditProfileComponent extends Component<IEditProfileComponentProps,
<div> <div>
{/* Edit profile dialog */} {/* Edit profile dialog */}
<Dialog <Dialog
PaperProps={{ className: classes.fullPageXs }}
key='Edit-Profile' key='Edit-Profile'
open={this.props.open!} open={this.props.open!}
onClose={this.props.onRequestClose} onClose={this.props.onRequestClose}
maxWidth='sm' maxWidth='sm'
> >
<DialogContent> <DialogContent className={classes.dialogContentRoot}>
{/* Banner */} {/* Banner */}
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
<ImgCover width='100%' height='250px' borderRadius='2px' fileName={this.state.banner} /> <ImgCover width='100%' height='250px' borderRadius='2px' fileName={this.state.banner} />
@@ -354,40 +439,88 @@ export class EditProfileComponent extends Component<IEditProfileComponentProps,
{/* Edit user information box*/} {/* Edit user information box*/}
<Paper style={this.styles.paper} elevation={1}> <Paper style={this.styles.paper} elevation={1}>
<div style={this.styles.title as any}>{translate!('profile.personalInformationLabel')}</div> <div style={this.styles.title as any}>{translate!('profile.personalInformationLabel')}</div>
<div style={this.styles.box}> <div className={classes.box}>
<FormControl aria-describedby='fullNameInputError'> <FormControl fullWidth aria-describedby='fullNameInputError'>
<InputLabel htmlFor='fullNameInput'>{translate!('profile.fullName')}</InputLabel> <InputLabel htmlFor='fullNameInput'>{translate!('profile.fullName')}</InputLabel>
<Input id='fullNameInput' <Input id='fullNameInput'
onChange={this.handleInputChange} onChange={this.handleInputChange}
name='fullNameInput' name='fullNameInput'
value={this.state.fullNameInput} /> value={this.state.fullNameInput}
/>
<FormHelperText id='fullNameInputError'>{this.state.fullNameInputError}</FormHelperText> <FormHelperText id='fullNameInputError'>{this.state.fullNameInputError}</FormHelperText>
</FormControl> </FormControl>
</div> </div>
<br /> <div className={classes.box}>
<div style={this.styles.box}> <FormControl fullWidth aria-describedby='tagLineInputError'>
<FormControl aria-describedby='tagLineInputError'>
<InputLabel htmlFor='tagLineInput'>{translate!('profile.tagline')}</InputLabel> <InputLabel htmlFor='tagLineInput'>{translate!('profile.tagline')}</InputLabel>
<Input id='tagLineInput' <Input id='tagLineInput'
onChange={this.handleInputChange} onChange={this.handleInputChange}
name='tagLineInput' name='tagLineInput'
value={this.state.tagLineInput} /> value={this.state.tagLineInput}
/>
<FormHelperText id='tagLineInputError'>{this.state.fullNameInputError}</FormHelperText> <FormHelperText id='tagLineInputError'>{this.state.fullNameInputError}</FormHelperText>
</FormControl> </FormControl>
</div> </div>
<div className={classes.box}>
<TextField
className={classes.bottomTextSpace}
onChange={this.handleInputChange}
name='companyName'
value={companyName}
label={translate!('profile.companyName')}
fullWidth
/>
</div>
<div className={classes.box}>
<TextField
className={classes.bottomTextSpace}
onChange={this.handleInputChange}
name='twitterId'
value={twitterId}
label={translate!('profile.twitterId')}
fullWidth
/>
</div>
<div className={classes.box}>
<TextField
className={classes.bottomTextSpace}
onChange={this.handleInputChange}
name='webUrl'
value={webUrl}
label={translate!('profile.webUrl')}
fullWidth
/>
</div>
<div className={classes.box}>
<DayPickerInput
classNames={{ container: classes.dayPicker, overlay: '' }}
value={defaultBirthday}
onDayChange={this.handleBirthdayDateChange}
formatDate={formatDate}
parseDate={parseDate}
component={AppInput}
format='LL'
placeholder={`${moment().format('LL')}`}
dayPickerProps={{
locale: currentLanguage,
localeUtils: MomentLocaleUtils,
}}
/>
</div>
<br /> <br />
</Paper> </Paper>
<div style={{ height: '16px' }}></div> <div className={classes.bottomPaperSpace}></div>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions className={classes.fixedDownStickyXS}>
<Button onClick={this.props.onRequestClose} > {translate!('profile.cancelButton')} </Button> <Button onClick={this.props.onRequestClose} > {translate!('profile.cancelButton')} </Button>
<Button variant='raised' color='primary' onClick={this.handleUpdate} style={this.styles.updateButton}> {translate!('profile.updateButton')} </Button> <Button variant='raised' color='primary' onClick={this.handleUpdate} style={this.styles.updateButton}> {translate!('profile.updateButton')} </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
{/* Image gallery for banner*/} {/* Image gallery for banner*/}
<Dialog <Dialog
PaperProps={{ className: classes.fullPageXs }}
open={this.state.openBanner} open={this.state.openBanner}
onClose={this.handleCloseBannerGallery} onClose={this.handleCloseBannerGallery}
@@ -400,6 +533,7 @@ export class EditProfileComponent extends Component<IEditProfileComponentProps,
{/* Image gallery for avatar */} {/* Image gallery for avatar */}
<Dialog <Dialog
PaperProps={{ className: classes.fullPageXs }}
open={this.state.openAvatar} open={this.state.openAvatar}
onClose={this.handleCloseAvatarGallery} onClose={this.handleCloseAvatarGallery}
> >
@@ -434,15 +568,17 @@ const mapDispatchToProps = (dispatch: any, ownProps: IEditProfileComponentProps)
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any, ownProps: IEditProfileComponentProps) => { const mapStateToProps = (state: Map<string, any>, ownProps: IEditProfileComponentProps) => {
const uid = state.getIn(['authorize', 'uid'])
return { return {
translate: getTranslate(state.locale), currentLanguage: getActiveLanguage(state.get('locale')).code,
open: state.user.openEditProfile, translate: getTranslate(state.get('locale')),
info: state.user.info[state.authorize.uid], open: state.getIn(['user', 'openEditProfile'], false),
avatarURL: state.imageGallery.imageURLList info: state.getIn(['user', 'info', uid]),
avatarURL: state.getIn(['imageGallery', 'imageURLList'])
} }
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(EditProfileComponent as any) as any) export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles as any)(EditProfileComponent as any) as any)

View File

@@ -65,4 +65,9 @@ export interface IEditProfileComponentProps {
* Translate to locale string * Translate to locale string
*/ */
translate?: (state: any) => any translate?: (state: any) => any
/**
* Current locale language
*/
currentLanguage?: string
} }

View File

@@ -65,4 +65,29 @@ export interface IEditProfileComponentState {
*/ */
openAvatar: boolean openAvatar: boolean
/**
* Default birth day
*/
defaultBirthday: any
/**
* Seleted birth day
*/
selectedBirthday: number
/**
* Web URL
*/
webUrl: string
/**
* User company name
*/
companyName: string
/**
* User twitter id
*/
twitterId: string
} }

View File

@@ -2,9 +2,10 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Paper from 'material-ui/Paper' import Paper from '@material-ui/core/Paper'
import InfiniteScroll from 'react-infinite-scroller' import InfiniteScroll from 'react-infinite-scroller'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import {Map} from 'immutable'
// - Import app components // - Import app components
import UserBoxList from 'components/userBoxList' import UserBoxList from 'components/userBoxList'
@@ -13,9 +14,10 @@ import LoadMoreProgressComponent from 'layouts/loadMoreProgress'
// - Import API // - Import API
// - Import actions // - Import actions
import * as userActions from 'actions/userActions' import * as userActions from 'store/actions/userActions'
import { IFindPeopleComponentProps } from './IFindPeopleComponentProps' import { IFindPeopleComponentProps } from './IFindPeopleComponentProps'
import { IFindPeopleComponentState } from './IFindPeopleComponentState' import { IFindPeopleComponentState } from './IFindPeopleComponentState'
import { UserTie } from 'core/domain/circles/userTie'
/** /**
* Create component class * Create component class
@@ -50,7 +52,7 @@ export class FindPeopleComponent extends Component<IFindPeopleComponentProps, IF
*/ */
render () { render () {
const {hasMorePeople, translate} = this.props const {hasMorePeople, translate} = this.props
const peopleInfo = Map<string, UserTie>(this.props.peopleInfo!)
return ( return (
<div> <div>
<InfiniteScroll <InfiniteScroll
@@ -63,11 +65,11 @@ export class FindPeopleComponent extends Component<IFindPeopleComponentProps, IF
<div className='tracks'> <div className='tracks'>
{this.props.peopleInfo && Object.keys(this.props.peopleInfo).length !== 0 ? (<div> {peopleInfo && peopleInfo.count() > 0 ? (<div>
<div className='profile__title'> <div className='profile__title'>
{translate!('people.suggestionsForYouLabel')} {translate!('people.suggestionsForYouLabel')}
</div> </div>
<UserBoxList users={this.props.peopleInfo}/> <UserBoxList users={peopleInfo}/>
<div style={{ height: '24px' }}></div> <div style={{ height: '24px' }}></div>
</div>) : (<div className='g__title-center'> </div>) : (<div className='g__title-center'>
{translate!('people.nothingToShowLabel')} {translate!('people.nothingToShowLabel')}
@@ -98,11 +100,13 @@ const mapDispatchToProps = (dispatch: any, ownProps: IFindPeopleComponentProps)
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any, ownProps: IFindPeopleComponentProps) => { const mapStateToProps = (state: any, ownProps: IFindPeopleComponentProps) => {
const {people, info} = state.user const people = state.getIn(['user', 'people'])
const hasMorePeople = state.getIn(['user', 'people', 'hasMoreData' ], true)
const info: Map<string, UserTie> = state.getIn(['user', 'info'])
return { return {
translate: getTranslate(state.locale), translate: getTranslate(state.get('locale')),
peopleInfo: info, peopleInfo: info,
hasMorePeople: people.hasMoreData hasMorePeople
} }
} }

View File

@@ -1,4 +1,5 @@
import { Profile } from 'core/domain/users/profile' import { Profile } from 'core/domain/users/profile'
import { UserTie } from 'core/domain/circles'
export interface IFindPeopleComponentProps { export interface IFindPeopleComponentProps {
@@ -11,11 +12,8 @@ export interface IFindPeopleComponentProps {
/** /**
* Users' profile * Users' profile
*
* @type {{[userId: string]: Profile}}
* @memberof IFindPeopleComponentProps
*/ */
peopleInfo?: {[userId: string]: Profile} peopleInfo?: Map<string, UserTie>
/** /**
* If there are more people {true} or not {false} * If there are more people {true} or not {false}

View File

@@ -3,6 +3,7 @@ import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import {Map} from 'immutable'
// - Import app components // - Import app components
import UserBoxList from 'components/userBoxList' import UserBoxList from 'components/userBoxList'
@@ -46,13 +47,14 @@ export class FollowersComponent extends Component<IFollowersComponentProps,IFoll
*/ */
render () { render () {
const {translate} = this.props const {translate} = this.props
const followers = this.props.followers!
return ( return (
<div> <div>
{(this.props.followers && Object.keys(this.props.followers).length !== 0) ? (<div> {(followers && followers.keySeq().count() !== 0) ? (<div>
<div className='profile__title'> <div className='profile__title'>
{translate!('people.followersLabel')} {translate!('people.followersLabel')}
</div> </div>
<UserBoxList users={this.props.followers} /> <UserBoxList users={followers} />
<div style={{ height: '24px' }}></div> <div style={{ height: '24px' }}></div>
</div>) </div>)
: (<div className='g__title-center'> : (<div className='g__title-center'>
@@ -81,13 +83,13 @@ const mapDispatchToProps = (dispatch: any,ownProps: IFollowersComponentProps) =>
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any,ownProps: IFollowersComponentProps) => { const mapStateToProps = (state: Map<string, any>,ownProps: IFollowersComponentProps) => {
const {circle, authorize, server} = state
const { uid } = state.authorize const uid = state.getIn(['authorize', 'uid'], 0)
const circles: { [circleId: string]: Circle } = circle ? (circle.circleList || {}) : {} const circles: { [circleId: string]: Circle } = state.getIn(['circle', 'circleList'], {})
const followers = circle ? circle.userTieds : {} const followers = state.getIn(['circle', 'userTieds'], {})
return{ return{
translate: getTranslate(state.locale), translate: getTranslate(state.get('locale')),
followers followers
} }
} }

View File

@@ -1,14 +1,12 @@
import { UserTie } from 'core/domain/circles' import { UserTie } from 'core/domain/circles'
import {Map} from 'immutable'
export interface IFollowersComponentProps { export interface IFollowersComponentProps {
/** /**
* User followers info * User followers info
*
* @type {{[userId: string]: UserTie}}
* @memberof IFindPeopleComponentProps
*/ */
followers?: {[userId: string]: UserTie} followers?: Map<string, UserTie>
/** /**
* Translate to locale string * Translate to locale string

View File

@@ -3,6 +3,7 @@ import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import {Map} from 'immutable'
// - Import app components // - Import app components
import UserBoxList from 'components/userBoxList' import UserBoxList from 'components/userBoxList'
@@ -46,13 +47,14 @@ export class FollowingComponent extends Component<IFollowingComponentProps,IFoll
*/ */
render () { render () {
const {translate} = this.props const {translate} = this.props
const followingUsers = Map(this.props.followingUsers!)
return ( return (
<div> <div>
{(this.props.followingUsers && Object.keys(this.props.followingUsers).length !== 0 ) ? (<div> {(followingUsers && followingUsers.keySeq().count() !== 0 ) ? (<div>
<div className='profile__title'> <div className='profile__title'>
{translate!('people.followingLabel')} {translate!('people.followingLabel')}
</div> </div>
<UserBoxList users={this.props.followingUsers} /> <UserBoxList users={followingUsers} />
<div style={{ height: '24px' }}></div> <div style={{ height: '24px' }}></div>
</div>) : (<div className='g__title-center'> </div>) : (<div className='g__title-center'>
@@ -81,13 +83,13 @@ const mapDispatchToProps = (dispatch: any,ownProp: IFollowingComponentProps) =>
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any,ownProps: IFollowingComponentProps) => { const mapStateToProps = (state: Map<string, any>,ownProps: IFollowingComponentProps) => {
const {circle, authorize, server} = state
const { uid } = state.authorize const uid = state.getIn(['authorize', 'uid'], 0)
const circles: { [circleId: string]: Circle } = circle ? (circle.circleList || {}) : {} const circles: { [circleId: string]: Circle } = state.getIn(['circle', 'circleList'], {})
const followingUsers = circle ? circle.userTies : {} const followingUsers = state.getIn(['circle', 'userTies'], {})
return { return {
translate: getTranslate(state.locale), translate: getTranslate(state.get('locale')),
uid, uid,
circles, circles,
followingUsers followingUsers

View File

@@ -1,8 +1,9 @@
import { UserTie } from 'core/domain/circles' import { UserTie } from 'core/domain/circles'
import {Map} from 'immutable'
export interface IFollowingComponentProps { export interface IFollowingComponentProps {
followingUsers?: {[userId: string]: UserTie} followingUsers?: Map<string, UserTie>
/** /**
* Translate to locale string * Translate to locale string

View File

@@ -1,283 +0,0 @@
// - Import react components
import { HomeRouter } from 'routes'
import React, { Component } from 'react'
import _ from 'lodash'
import { Route, Switch, withRouter, Redirect, NavLink } from 'react-router-dom'
import { connect } from 'react-redux'
import { push } from 'react-router-redux'
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import config from 'src/config'
import Menu from 'material-ui/Menu'
import { MenuList, MenuItem } from 'material-ui/Menu'
import { ListItemIcon, ListItemText } from 'material-ui/List'
import Divider from 'material-ui/Divider'
import SvgArrowLeft from 'material-ui-icons/KeyboardArrowLeft'
import SvgHome from 'material-ui-icons/Home'
import SvgFeedback from 'material-ui-icons/Feedback'
import SvgSettings from 'material-ui-icons/Settings'
import SvgAccountCircle from 'material-ui-icons/AccountCircle'
import SvgPeople from 'material-ui-icons/People'
// - Import app components
import Sidebar from 'components/sidebar'
import StreamComponent from 'components/stream'
import HomeHeader from 'components/homeHeader'
import SidebarContent from 'components/sidebarContent'
import SidebarMain from 'components/sidebarMain'
import Profile from 'components/profile'
import PostPage from 'components/postPage'
import People from 'components/people'
// - Import API
// - Import actions
// - Import actions
import {
authorizeActions,
imageGalleryActions,
postActions,
commentActions,
voteActions,
userActions,
globalActions,
circleActions,
notifyActions
} from 'actions'
import { IHomeComponentProps } from './IHomeComponentProps'
import { IHomeComponentState } from './IHomeComponentState'
// - Create Home component class
export class HomeComponent extends Component<IHomeComponentProps, IHomeComponentState> {
// Constructor
constructor (props: IHomeComponentProps) {
super(props)
// Default state
this.state = {
sidebarOpen: () => _,
sidebarStatus: true,
sidebarOverlay: false
}
// Binding function to `this`
this.sidebar = this.sidebar.bind(this)
this.sidebarStatus = this.sidebarStatus.bind(this)
this.sidebarOverlay = this.sidebarOverlay.bind(this)
this.handleCloseSidebar = this.handleCloseSidebar.bind(this)
}
/**
* handle close sidebar
*/
handleCloseSidebar = () => {
this.state.sidebarOpen!(false, 'overlay')
}
/**
* Change sidebar overlay status
* @param {boolean} status if is true, the sidebar is on overlay status
*/
sidebarOverlay = (status: boolean) => {
this.setState({
sidebarOverlay: status
})
}
/**
* Pass the function to change sidebar status
* @param {boolean} open is a function callback to change sidebar status out of sidebar component
*/
sidebar = (open: (status: boolean, source: string) => void) => {
this.setState({
sidebarOpen: open
})
}
/**
* Change sidebar status if is open or not
* @param {boolean} status is true, if the sidebar is open
*/
sidebarStatus = (status: boolean) => {
this.setState({
sidebarStatus: status
})
}
componentWillMount () {
const { global, clearData, loadData, authed, defaultDataEnable, isVerifide, goTo } = this.props
if (!authed) {
goTo!('/login')
return
}
if (!isVerifide) {
goTo!('/emailVerification')
} else if (!global.defaultLoadDataStatus) {
clearData!()
loadData!()
defaultDataEnable!()
}
}
/**
* Render DOM component
*
* @returns DOM
*
* @memberof Home
*/
render () {
const HR = HomeRouter as any
const { loaded, authed, loadDataStream, mergedPosts, hasMorePosts, showSendFeedback, translate } = this.props
return (
<div id='home'>
<HomeHeader sidebar={this.state.sidebarOpen} sidebarStatus={this.state.sidebarStatus} />
<Sidebar overlay={this.sidebarOverlay} open={this.sidebar} status={this.sidebarStatus}>
<SidebarContent>
<MenuList style={{ color: 'rgb(117, 117, 117)', width: '210px' }}>
{this.state.sidebarOverlay
? <div>
<MenuItem style={{ color: 'rgb(117, 117, 117)' }}>
<ListItemIcon>
<SvgArrowLeft viewBox='0 3 24 24' style={{ color: '#fff', marginLeft: '15px', width: '32px', height: '32px', cursor: 'pointer' }} />
</ListItemIcon>
<ListItemText inset
primary={<span style={{ color: 'rgb(117, 117, 117)' }}
className='sidebar__title'>{config.settings.appName}</span>} />
</MenuItem>
<Divider /></div>
: ''
}
<NavLink to='/'>
<MenuItem style={{ color: 'rgb(117, 117, 117)' }}>
<ListItemIcon>
<SvgHome />
</ListItemIcon>
<ListItemText inset primary={translate!('sidebar.home')} />
</MenuItem>
</NavLink>
<NavLink to={`/${this.props.uid}`}>
<MenuItem style={{ color: 'rgb(117, 117, 117)' }}>
<ListItemIcon>
<SvgAccountCircle />
</ListItemIcon>
<ListItemText inset primary={translate!('sidebar.profile')} />
</MenuItem>
</NavLink>
<NavLink to='/people'>
<MenuItem style={{ color: 'rgb(117, 117, 117)' }}>
<ListItemIcon>
<SvgPeople />
</ListItemIcon>
<ListItemText inset primary={translate!('sidebar.people')} />
</MenuItem>
</NavLink>
<Divider />
<NavLink to='/settings'>
<MenuItem style={{ color: 'rgb(117, 117, 117)' }}>
<ListItemIcon>
<SvgSettings />
</ListItemIcon>
<ListItemText inset primary={translate!('sidebar.settings')} />
</MenuItem>
</NavLink>
<MenuItem onClick={() => showSendFeedback!()} style={{ color: 'rgb(117, 117, 117)' }}>
<ListItemIcon>
<SvgFeedback />
</ListItemIcon>
<ListItemText inset primary={translate!('sidebar.sendFeedback')} />
</MenuItem>
</MenuList>
</SidebarContent>
<SidebarMain>
<HR enabled={loaded!} data={{ mergedPosts, loadDataStream, hasMorePosts }} />
</SidebarMain>
</Sidebar>
</div>
)
}
}
// - Map dispatch to props
const mapDispatchToProps = (dispatch: any, ownProps: IHomeComponentProps) => {
return {
loadDataStream:
(page: number, limit: number) => dispatch(postActions.dbGetPosts(page, limit)),
loadData: () => {
dispatch(postActions.dbGetPosts())
dispatch(imageGalleryActions.dbGetImageGallery())
dispatch(userActions.dbGetUserInfo())
dispatch(notifyActions.dbGetNotifications())
dispatch(circleActions.dbGetCircles())
dispatch(circleActions.dbGetUserTies())
dispatch(circleActions.dbGetFollowers())
},
clearData: () => {
dispatch(imageGalleryActions.clearAllData())
dispatch(postActions.clearAllData())
dispatch(userActions.clearAllData())
dispatch(notifyActions.clearAllNotifications())
dispatch(circleActions.clearAllCircles())
dispatch(globalActions.clearTemp())
},
defaultDataDisable: () => {
dispatch(globalActions.defaultDataDisable())
},
defaultDataEnable: () => {
dispatch(globalActions.defaultDataEnable())
},
goTo: (url: string) => dispatch(push(url)),
showSendFeedback: () => dispatch(globalActions.showSendFeedback()),
hideSendFeedback: () => dispatch(globalActions.hideSendFeedback())
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state: any, ownProps: IHomeComponentProps) => {
const { authorize, global, user, post, imageGallery, notify, circle } = state
const { uid } = authorize
let mergedPosts = {}
const circles = circle ? (circle.circleList || {}) : {}
const followingUsers = circle ? circle.userTies : {}
const posts = post.userPosts ? post.userPosts[authorize.uid] : {}
const hasMorePosts = post.stream.hasMoreData
Object.keys(followingUsers).forEach((userId) => {
let newPosts = post.userPosts ? post.userPosts[userId] : {}
_.merge(mergedPosts, newPosts)
})
_.merge(mergedPosts, posts)
return {
authed: authorize.authed,
isVerifide: authorize.isVerifide,
mainStyle: global.sidebarMainStyle,
translate: getTranslate(state.locale),
currentLanguage: getActiveLanguage(state.locale).code,
mergedPosts,
global,
hasMorePosts,
loaded: user.loaded && imageGallery.loaded && notify.loaded && circle.loaded
}
}
// - Connect component to redux store
export default withRouter<any>(connect(mapStateToProps, mapDispatchToProps)(HomeComponent as any)) as typeof HomeComponent

View File

@@ -1,27 +0,0 @@
export interface IHomeComponentState {
/**
* Change sidebar status to {open(status:true)/close(status:false)}
*
* @type {(status: boolean, state: string)}
* @memberof IHomeComponentState
*/
sidebarOpen: (status: boolean, source: string) => void
/**
* Sidebar status
*
* @type {boolean}
* @memberof IHomeComponentState
*/
sidebarStatus: boolean
/**
* Sidebar overlay status
*
* @type {boolean}
* @memberof IHomeComponentState
*/
sidebarOverlay: boolean
}

View File

@@ -2,20 +2,26 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { NavLink } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import SvgDehaze from 'material-ui-icons/Dehaze' import classNames from 'classnames'
import { grey, blue } from 'material-ui/colors' import { Map } from 'immutable'
import Toolbar from 'material-ui/Toolbar'
import IconButton from 'material-ui/IconButton' // - Material UI
import Popover from 'material-ui/Popover' import SvgDehaze from '@material-ui/icons/Dehaze'
import AppBar from 'material-ui/AppBar' import { grey, blue } from '@material-ui/core/colors'
import Menu, { MenuList, MenuItem } from 'material-ui/Menu' import Toolbar from '@material-ui/core/Toolbar'
import Paper from 'material-ui/Paper' import IconButton from '@material-ui/core/IconButton'
import NotificationsIcon from 'material-ui-icons/Notifications' import Popover from '@material-ui/core/Popover'
import EventListener, { withOptions } from 'react-event-listener' import AppBar from '@material-ui/core/AppBar'
import Tooltip from 'material-ui/Tooltip' import MenuList from '@material-ui/core/MenuList'
import Typography from 'material-ui/Typography' import MenuItem from '@material-ui/core/MenuItem'
import Menu from '@material-ui/core/Menu'
import Paper from '@material-ui/core/Paper'
import Hidden from '@material-ui/core/Hidden'
import NotificationsIcon from '@material-ui/icons/Notifications'
import Tooltip from '@material-ui/core/Tooltip'
import Typography from '@material-ui/core/Typography'
import { Manager, Target, Popper } from 'react-popper' import { Manager, Target, Popper } from 'react-popper'
import { withStyles } from 'material-ui/styles' import { withStyles } from '@material-ui/core/styles'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import config from 'src/config' import config from 'src/config'
@@ -24,8 +30,8 @@ import UserAvatarComponent from 'components/userAvatar'
import Notify from 'components/notify' import Notify from 'components/notify'
// - Import actions // - Import actions
import * as globalActions from 'actions/globalActions' import * as globalActions from 'store/actions/globalActions'
import { authorizeActions } from 'actions' import { authorizeActions } from 'store/actions'
import { IHomeHeaderComponentProps } from './IHomeHeaderComponentProps' import { IHomeHeaderComponentProps } from './IHomeHeaderComponentProps'
import { IHomeHeaderComponentState } from './IHomeHeaderComponentState' import { IHomeHeaderComponentState } from './IHomeHeaderComponentState'
@@ -92,13 +98,8 @@ export class HomeHeaderComponent extends Component<IHomeHeaderComponentProps, IH
// On click toggle sidebar // On click toggle sidebar
onToggleSidebar = () => { onToggleSidebar = () => {
if (this.props.sidebarStatus) { const {onToggleDrawer} = this.props
this.props.sidebar!(false, 'onToggle') onToggleDrawer()
} else {
this.props.sidebar!(true, 'onToggle')
}
} }
/** /**
@@ -153,29 +154,19 @@ export class HomeHeaderComponent extends Component<IHomeHeaderComponentProps, IH
}) })
} }
handleKeyUp = () => {
// TODO: Handle key up on press ESC to close menu
}
/** /**
* Handle resize event for window to manipulate home header status * Handle resize event for window to manipulate home header status
* @param {event} evt is the event is passed by winodw resize event * @param {event} evt is the event is passed by winodw resize event
*/ */
handleResize = (event: any) => { handleResize = (event: any) => {
const {drawerStatus} = this.props
// Set initial state // Set initial state
let width = window.innerWidth let width = window.innerWidth
if (width >= 600 && !this.state.showTitle) { if (width >= 600 && !drawerStatus) {
this.setState({ this.onToggleSidebar()
showTitle: true } else if (width < 600) {
})
} else if (width < 600 && this.state.showTitle) {
this.setState({
showTitle: false
})
} }
} }
@@ -185,16 +176,12 @@ export class HomeHeaderComponent extends Component<IHomeHeaderComponentProps, IH
// Render app DOM component // Render app DOM component
render () { render () {
const { classes , translate} = this.props const { classes , translate, theme} = this.props
const anchor = theme.direction === 'rtl' ? 'right' : 'left'
return ( return (
<AppBar position='fixed' color='secondary'> <AppBar position='fixed' color='secondary'>
<Toolbar> <Toolbar>
<EventListener
target='window'
onResize={this.handleResize}
onKeyUp={this.handleKeyUp}
/>
{/* Left side */} {/* Left side */}
<IconButton onClick={this.onToggleSidebar} > <IconButton onClick={this.onToggleSidebar} >
@@ -205,7 +192,9 @@ export class HomeHeaderComponent extends Component<IHomeHeaderComponentProps, IH
{config.settings.appName} {config.settings.appName}
</Typography> </Typography>
<div className='homeHeader__title-root'> <div className='homeHeader__title-root'>
{this.state.showTitle ? <div className='homeHeader__title'>{this.props.title}</div> : ''} <Hidden smDown>
<div className={classNames({'homeHeader__title-left': anchor === 'left', 'homeHeader__title-right': anchor === 'right' })}>{this.props.title}</div>
</Hidden>
</div> </div>
{/* Notification */} {/* Notification */}
@@ -270,21 +259,23 @@ const mapDispatchToProps = (dispatch: Function, ownProps: IHomeHeaderComponentPr
} }
// - Map state to props // - Map state to props
const mapStateToProps = (state: any, ownProps: IHomeHeaderComponentProps) => { const mapStateToProps = (state: Map<string,any>, ownProps: IHomeHeaderComponentProps) => {
let notifyCount = state.notify.userNotifies const uid = state.getIn(['authorize', 'uid'], 0)
? Object const userNotifies: Map<string, any> = state.getIn(['notify','userNotifies'])
.keys(state.notify.userNotifies) let notifyCount = userNotifies
.filter((key) => !state.notify.userNotifies[key].isSeen).length ? userNotifies
.filter((notification) => !notification.get('isSeen', false)).count()
: 0 : 0
const user = state.getIn(['user', 'info', uid], {})
return { return {
translate: getTranslate(state.locale), translate: getTranslate(state.get('locale')),
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : '', avatar: user.avatar || '',
fullName: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].fullName : '', fullName: user.fullName || '',
title: state.global.headerTitle, title: state.getIn(['global', 'headerTitle'], ''),
notifyCount notifyCount
} }
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(HomeHeaderComponent as any) as any) export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles, { withTheme: true })(HomeHeaderComponent as any) as any)

View File

@@ -7,7 +7,7 @@ export interface IHomeHeaderComponentProps {
* @type {boolean} * @type {boolean}
* @memberof IHomeHeaderComponentProps * @memberof IHomeHeaderComponentProps
*/ */
sidebarStatus?: boolean drawerStatus: boolean
/** /**
* Logout user * Logout user
@@ -59,12 +59,17 @@ export interface IHomeHeaderComponentProps {
* *
* @memberof IHomeHeaderComponentProps * @memberof IHomeHeaderComponentProps
*/ */
sidebar?: (status: boolean, source: string) => void onToggleDrawer: () => void
/** /**
* Material ui theme style * Material ui theme style
*/ */
classes?: any classes?: any
/**
* Theme
*/
theme?: any
/** /**
* Translate to locale string * Translate to locale string

View File

@@ -1,4 +1,5 @@
import { Image } from 'core/domain/imageGallery' import { Image } from 'core/domain/imageGallery'
import {Map, Collection, List} from 'immutable'
export interface IImageGalleryComponentProps { export interface IImageGalleryComponentProps {
@@ -33,11 +34,13 @@ export interface IImageGalleryComponentProps {
/** /**
* List of image in image gallery * List of image in image gallery
*
* @type {Image[]}
* @memberof IImageGalleryComponentProps
*/ */
images?: Image[] images?: List<Image>
/**
* Styles
*/
classes?: any
/** /**
* Translate to locale string * Translate to locale string

View File

@@ -2,20 +2,24 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import GridList, { GridListTile, GridListTileBar } from 'material-ui/GridList' import GridList from '@material-ui/core/GridList'
import IconButton from 'material-ui/IconButton' import GridListTileBar from '@material-ui/core/GridListTileBar'
import StarBorder from 'material-ui-icons/StarBorder' import GridListTile from '@material-ui/core/GridListTile'
import Button from 'material-ui/Button' import IconButton from '@material-ui/core/IconButton'
import SvgUpload from 'material-ui-icons/CloudUpload' import StarBorder from '@material-ui/icons/StarBorder'
import SvgAddImage from 'material-ui-icons/AddAPhoto' import Button from '@material-ui/core/Button'
import SvgDelete from 'material-ui-icons/Delete' import SvgUpload from '@material-ui/icons/CloudUpload'
import { grey } from 'material-ui/colors' import SvgAddImage from '@material-ui/icons/AddAPhoto'
import SvgDelete from '@material-ui/icons/Delete'
import { grey } from '@material-ui/core/colors'
import { withStyles } from '@material-ui/core/styles'
import uuid from 'uuid' import uuid from 'uuid'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import {Map} from 'immutable'
// - Import actions // - Import actions
import * as imageGalleryActions from 'actions/imageGalleryActions' import * as imageGalleryActions from 'store/actions/imageGalleryActions'
import * as globalActions from 'actions/globalActions' import * as globalActions from 'store/actions/globalActions'
// - Import app components // - Import app components
import Img from 'components/img' import Img from 'components/img'
@@ -26,6 +30,17 @@ import { IImageGalleryComponentProps } from './IImageGalleryComponentProps'
import { IImageGalleryComponentState } from './IImageGalleryComponentState' import { IImageGalleryComponentState } from './IImageGalleryComponentState'
import { Image } from 'core/domain/imageGallery' import { Image } from 'core/domain/imageGallery'
const styles = (theme: any) => ({
fullPageXs: {
[theme.breakpoints.down('xs')]: {
width: '100%',
height: '100%',
margin: 0,
overflowY: 'auto'
}
}
})
/** /**
* Create ImageGallery component class * Create ImageGallery component class
*/ */
@@ -149,7 +164,6 @@ export class ImageGalleryComponent extends Component<IImageGalleryComponentProps
} }
imageList = () => { imageList = () => {
return this.props.images!.map((image: Image, index) => { return this.props.images!.map((image: Image, index) => {
return ( return (
@@ -242,14 +256,16 @@ const mapDispatchToProps = (dispatch: any, ownProps: IImageGalleryComponentProps
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any) => { const mapStateToProps = (state: Map<string, any>) => {
const uid = state.getIn(['authorize', 'uid'])
const currentUser = state.getIn(['user', 'info', uid])
return { return {
translate: getTranslate(state.locale), translate: getTranslate(state.get('locale')),
images: state.imageGallery.images, images: state.getIn(['imageGallery', 'images']),
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : '' avatar: currentUser ? currentUser.avatar : ''
} }
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(ImageGalleryComponent as any) export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles as any)(ImageGalleryComponent as any) as any)

View File

@@ -2,16 +2,17 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import SvgImage from 'material-ui-icons/Image' import SvgImage from '@material-ui/icons/Image'
import { withStyles } from 'material-ui/styles' import { withStyles } from '@material-ui/core/styles'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import { Map } from 'immutable'
// - Import app components // - Import app components
// - Import API // - Import API
// - Import actions // - Import actions
import * as imageGalleryActions from 'actions/imageGalleryActions' import * as imageGalleryActions from 'store/actions/imageGalleryActions'
import { IImgComponentProps } from './IImgComponentProps' import { IImgComponentProps } from './IImgComponentProps'
import { IImgComponentState } from './IImgComponentState' import { IImgComponentState } from './IImgComponentState'
@@ -121,13 +122,13 @@ const mapDispatchToProps = (dispatch: any, ownProps: IImgComponentProps) => {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any, ownProps: IImgComponentProps) => { const mapStateToProps = (state: Map<string, any>, ownProps: IImgComponentProps) => {
return { return {
translate: getTranslate(state.locale), translate: getTranslate(state.get('locale')),
avatarURL: state.imageGallery.imageURLList, avatarURL: state.getIn(['imageGallery', 'imageURLList']),
imageRequests: state.imageGallery.imageRequests imageRequests: state.getIn(['imageGallery', 'imageRequests'])
} }
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(ImgComponent as any)as any) export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles as any)(ImgComponent as any)as any)

View File

@@ -2,15 +2,16 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import SvgImage from 'material-ui-icons/Image' import SvgImage from '@material-ui/icons/Image'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import {Map} from 'immutable'
// - Import app components // - Import app components
// - Import API // - Import API
// - Import actions // - Import actions
import * as imageGalleryActions from 'actions/imageGalleryActions' import * as imageGalleryActions from 'store/actions/imageGalleryActions'
import { IImgCoverComponentProps } from './IImgCoverComponentProps' import { IImgCoverComponentProps } from './IImgCoverComponentProps'
import { IImgCoverComponentState } from './IImgCoverComponentState' import { IImgCoverComponentState } from './IImgCoverComponentState'
@@ -153,9 +154,7 @@ const mapDispatchToProps = (dispatch: any, ownProps: IImgCoverComponentProps) =>
*/ */
const mapStateToProps = (state: any, ownProps: IImgCoverComponentProps) => { const mapStateToProps = (state: any, ownProps: IImgCoverComponentProps) => {
return { return {
translate: getTranslate(state.locale), translate: getTranslate(state.get('locale'))
avatarURL: state.imageGallery.imageURLList,
imageRequests: state.imageGallery.imageRequests
} }
} }

View File

@@ -1,263 +0,0 @@
// - Import external components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { NavLink, withRouter } from 'react-router-dom'
import { push } from 'react-router-redux'
import Paper from 'material-ui/Paper'
import TextField from 'material-ui/TextField'
import RaisedButton from 'material-ui/Button'
import Button from 'material-ui/Button'
import IconButton from 'material-ui/IconButton'
import Divider from 'material-ui/Divider'
import ActionAndroid from 'material-ui-icons/Android'
import { withStyles } from 'material-ui/styles'
import config from 'src/config'
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
// - Import actions
import * as authorizeActions from 'actions/authorizeActions'
import { ILoginComponentProps } from './ILoginComponentProps'
import { ILoginComponentState } from './ILoginComponentState'
import { OAuthType } from 'core/domain/authorize'
const styles = (theme: any) => ({
textField: {
minWidth: 280,
marginTop: 20
}
})
// - Create Login component class
export class LoginComponent extends Component<ILoginComponentProps,ILoginComponentState> {
styles = {
singinOptions: {
paddingBottom: 10,
justifyContent: 'space-around',
display: 'flex'
},
divider: {
marginBottom: 10,
marginTop: 15
},
restPassword: {
lineHeight: 6,
fontWeight: 400,
fontSize: 'small'
},
restPasswordLink: {
color: '#0095ff'
}
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor (props: ILoginComponentProps) {
super(props)
this.state = {
emailInput: '',
emailInputError: '',
passwordInput: '',
passwordInputError: '',
confirmInputError: ''
}
// Binding function to `this`
this.handleForm = this.handleForm.bind(this)
}
/**
* Handle data on input change
* @param {event} evt is an event of inputs of element on change
*/
handleInputChange = (event: any) => {
const target = event.target
const value = target.type === 'checkbox' ? target.checked : target.value
const name = target.name
this.setState({
[name]: value
})
switch (name) {
case 'emailInput':
this.setState({
emailInputError: ''
})
break
case 'passwordInput':
this.setState({
confirmInputError: '',
passwordInputError: ''
})
break
default:
}
}
/**
* Handle register form
*/
handleForm = () => {
const {translate} = this.props
let error = false
if (this.state.emailInput === '') {
this.setState({
emailInputError: translate!('login.emailRequiredError')
})
error = true
}
if (this.state.passwordInput === '') {
this.setState({
passwordInputError: translate!('login.passwordRequiredError')
})
error = true
}
if (!error) {
this.props.login!(
this.state.emailInput,
this.state.passwordInput
)
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render () {
const {classes, loginWithOAuth, translate} = this.props
const paperStyle = {
minHeight: 370,
width: 450,
textAlign: 'center',
display: 'block',
margin: 'auto'
}
return (
<form>
<h1 style={{
textAlign: 'center',
padding: '20px',
fontSize: '30px',
fontWeight: 500,
lineHeight: '32px',
margin: 'auto',
color: 'rgba(138, 148, 138, 0.2)'
}}>{config.settings.appName}</h1>
<div className='animate-bottom'>
<Paper style={paperStyle} elevation={1} >
<div style={{ padding: '48px 40px 36px' }}>
<div style={{
paddingLeft: '40px',
paddingRight: '40px'
}}>
<h2 style={{
textAlign: 'left',
paddingTop: '16px',
fontSize: '24px',
fontWeight: 400,
lineHeight: '32px',
margin: 0
}} className='zoomOutLCorner animated'>{translate!('login.title')}</h2>
</div>
<div style={this.styles.singinOptions as any}>
<IconButton
onClick={() => loginWithOAuth!(OAuthType.FACEBOOK)}
><div className='icon-fb icon'></div></IconButton>
<IconButton
onClick={() => loginWithOAuth!(OAuthType.GOOGLE)}
> <div className='icon-google icon'></div> </IconButton>
<IconButton
onClick={() => loginWithOAuth!(OAuthType.GITHUB)}
> <div className='icon-github icon'></div> </IconButton>
</div>
<Divider style={this.styles.divider} />
<TextField
className={classes.textField}
autoFocus
onChange={this.handleInputChange}
helperText={this.state.emailInputError}
error={this.state.emailInputError.trim() !== ''}
name='emailInput'
label={translate!('login.emailLabel')}
type='email'
tabIndex={1}
/><br />
<TextField
className={classes.textField}
onChange={this.handleInputChange}
helperText={this.state.passwordInputError}
error={this.state.passwordInputError.trim() !== ''}
name='passwordInput'
label={translate!('login.passwordLabel')}
type='password'
tabIndex={2}
/><br />
<br />
<br />
<div className='login__button-box'>
<div>
<Button onClick={this.props.signupPage} tabIndex={4}>{translate!('login.createAccountButton')}</Button>
</div>
<div >
<Button variant='raised' color='primary' onClick={this.handleForm} tabIndex={3} >{translate!('login.loginButton')}</Button>
</div>
</div>
<span style={this.styles.restPassword as any}>{translate!('login.forgetPasswordMessage')} <NavLink to='/resetPassword' style={this.styles.restPasswordLink}>{translate!('login.resetPasswordLabel')}</NavLink></span>
</div>
</Paper>
</div>
</form>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch: any, ownProps: ILoginComponentProps) => {
return {
login: (email: string, password: string) => {
dispatch(authorizeActions.dbLogin(email, password))
},
loginWithOAuth: (type: OAuthType) => dispatch(authorizeActions.dbLoginWithOAuth(type)),
signupPage: () => {
dispatch(push('/signup'))
}
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state: any, ownProps: ILoginComponentProps) => {
return {
translate: getTranslate(state.locale)
}
}
// - Connect component to redux store
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(LoginComponent as any) as any) as any)

View File

@@ -1,12 +1,5 @@
export interface IMasterLoadingComponentProps { export interface IMasterLoadingComponentProps {
error?: boolean
/** timedOut?: boolean
* Loading is active {true} or not {false} pastDelay?: boolean
*
* @type {boolean}
* @memberof IMasterLoadingComponentProps
*/
activeLoading: boolean
handleLoading: (status: boolean) => void
} }

View File

@@ -1,9 +1,12 @@
// - Import react components // - Import react components
import React, { Component } from 'react' import React, { Component } from 'react'
import { CircularProgress } from 'material-ui/Progress' import CircularProgress from '@material-ui/core/CircularProgress'
import Dialog from 'material-ui/Dialog' import Dialog from '@material-ui/core/Dialog'
import red from '@material-ui/core/colors/red'
import { IMasterLoadingComponentProps } from './IMasterLoadingComponentProps' import { IMasterLoadingComponentProps } from './IMasterLoadingComponentProps'
import { IMasterLoadingComponentState } from './IMasterLoadingComponentState' import { IMasterLoadingComponentState } from './IMasterLoadingComponentState'
import Grid from '@material-ui/core/Grid/Grid'
import { Typography } from '@material-ui/core'
// - Import app components // - Import app components
@@ -11,26 +14,78 @@ import { IMasterLoadingComponentState } from './IMasterLoadingComponentState'
export default class MasterLoadingComponent extends Component<IMasterLoadingComponentProps, IMasterLoadingComponentState> { export default class MasterLoadingComponent extends Component<IMasterLoadingComponentProps, IMasterLoadingComponentState> {
// Constructor // Constructor
constructor (props: IMasterLoadingComponentProps) { constructor(props: IMasterLoadingComponentProps) {
super(props) super(props)
// Binding functions to `this` // Binding functions to `this`
} }
loadProgress() {
const { error, timedOut, pastDelay } = this.props
if (error) {
return (
<Grid container>
<Grid item>
<CircularProgress style={{ color: red[500] }} size={50} />
</Grid>
<Grid item style={{ zIndex: 1 }}>
<Typography variant='title' color='primary' style={{ marginLeft: '15px' }} >
Unexpected Error Happened ...
</Typography>
</Grid>
</Grid>
)
} else if (timedOut) {
return (
<Grid container>
<Grid item>
<CircularProgress style={{ color: red[500] }} size={50} />
</Grid>
<Grid item style={{ zIndex: 1 }}>
<Typography variant='title' color='primary' style={{ marginLeft: '15px' }} >
It takes long time ...
</Typography>
</Grid>
</Grid>
)
} else if (pastDelay) {
return (
<Grid container>
<Grid item>
<CircularProgress size={50} />
</Grid>
<Grid item style={{ zIndex: 1 }}>
<Typography variant='title' color='primary' style={{ marginLeft: '15px' }} >
Loading...
</Typography>
</Grid>
</Grid>
)
} else {
return (
<Grid container>
<Grid item>
<CircularProgress size={50} />
</Grid>
<Grid item style={{ zIndex: 1 }}>
<Typography variant='title' color='primary' style={{ marginLeft: '15px' }} >
Loading...
</Typography>
</Grid>
</Grid>
)
}
}
// Render app DOM component // Render app DOM component
render () { render() {
const {activeLoading} = this.props
return ( return (
<div className='mLoading__loading' style={{ display: (activeLoading ? 'flex' : 'none') }}> <div className='mLoading__loading'>
<CircularProgress {
color='secondary' this.loadProgress()
size={50} }
variant='determinate'
value={25}
min={0}
max={50}
/>
</div> </div>
) )

View File

@@ -1,44 +1,30 @@
import { Profile } from 'core/domain/users' import { Profile } from 'core/domain/users'
import { Notification } from 'core/domain/notifications' import { Notification } from 'core/domain/notifications'
import {Map} from 'immutable'
export interface INotifyComponentProps { export interface INotifyComponentProps {
/** /**
* Notifications * Notifications
*
* @type {{[notificationId: string]: Notification}}
* @memberof INotifyComponentProps
*/ */
notifications?: {[notificationId: string]: Notification} notifications?: Map<string, any>
/** /**
* Users' profile * Users' profile
*
* @type {{[userId: string]: Profile}}
* @memberof INotifyComponentProps
*/ */
info?: {[userId: string]: Profile} info?: Map<string, Profile>
/** /**
* Close notification * Close notification
*
* @memberof INotifyComponentProps
*/ */
onRequestClose: () => void onRequestClose: () => void
/** /**
* User notifications popover is opem {true} or not {false} * User notifications popover is opem {true} or not {false}
*
* @type {boolean}
* @memberof INotifyComponentProps
*/ */
open: boolean open: boolean
/** /**
* Keep element * Keep element
*
* @type {*}
* @memberof INotifyComponentProps
*/ */
anchorEl?: any anchorEl?: any

View File

@@ -4,19 +4,25 @@ import PropTypes from 'prop-types'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import classNames from 'classnames' import classNames from 'classnames'
import { Manager, Target, Popper } from 'react-popper' import { Manager, Target, Popper } from 'react-popper'
import ClickAwayListener from 'material-ui/utils/ClickAwayListener' import ClickAwayListener from '@material-ui/core/ClickAwayListener'
import Grow from 'material-ui/transitions/Grow' import Grow from '@material-ui/core/Grow'
import { withStyles } from 'material-ui/styles' import { withStyles } from '@material-ui/core/styles'
import Typography from 'material-ui/Typography' import Typography from '@material-ui/core/Typography'
import Paper from 'material-ui/Paper' import Paper from '@material-ui/core/Paper'
import List, { ListItem, ListItemSecondaryAction, ListItemText } from 'material-ui/List' import ListItemText from '@material-ui/core/ListItemText'
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
import ListItem from '@material-ui/core/ListItem'
import List from '@material-ui/core/List'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemAvatar from '@material-ui/core/ListItemAvatar'
import { Map } from 'immutable'
// - Import app components // - Import app components
import NotifyItem from 'components/notifyItem' import NotifyItem from 'components/notifyItem'
// - Import API // - Import API
// - Import actions // - Import actions
import * as userActions from 'actions/userActions' import * as userActions from 'store/actions/userActions'
import { INotifyComponentProps } from './INotifyComponentProps' import { INotifyComponentProps } from './INotifyComponentProps'
import { INotifyComponentState } from './INotifyComponentState' import { INotifyComponentState } from './INotifyComponentState'
@@ -52,8 +58,17 @@ const styles = (theme: any) => ({
}, },
list: { list: {
maxHeight: 380, maxHeight: 380,
overflowY: 'auto' overflowY: 'auto',
width: '98%'
},
fullPageXs: {
[theme.breakpoints.down('xs')]: {
width: '100%',
height: '100%',
margin: 0,
overflowY: 'auto'
}
} }
}) })
@@ -98,20 +113,22 @@ export class NotifyComponent extends Component<INotifyComponentProps, INotifyCom
} }
notifyItemList = () => { notifyItemList = () => {
let { notifications, info, onRequestClose } = this.props let { info, onRequestClose } = this.props
let notifications: Map<string, Map<string, any>> = this.props.notifications!
let parsedDOM: any[] = [] let parsedDOM: any[] = []
if (notifications) { if (notifications) {
Object.keys(notifications).forEach((key) => { notifications.forEach((notification, key) => {
const { notifierUserId } = notifications![key] const notifierUserId = notification!.get('notifierUserId')
const userInfo = info!.get(notifierUserId)
parsedDOM.push( parsedDOM.push(
<NotifyItem <NotifyItem
key={key} key={key}
description={(notifications![key] ? notifications![key].description || '' : '')} description={notification!.get('description', '')}
fullName={(info![notifierUserId] ? info![notifierUserId].fullName || '' : '')} fullName={(userInfo ? userInfo.fullName || '' : '')}
avatar={(info![notifierUserId] ? info![notifierUserId].avatar || '' : '')} avatar={(userInfo ? userInfo.avatar || '' : '')}
id={key} id={key!}
isSeen={(notifications![key] ? notifications![key].isSeen || false : false)} isSeen={notification!.get('isSeen', false)}
url={(notifications![key] ? notifications![key].url || '' : '')} url={notification!.get('url')}
notifierUserId={notifierUserId} notifierUserId={notifierUserId}
closeNotify={onRequestClose} closeNotify={onRequestClose}
/> />
@@ -171,10 +188,10 @@ const mapDispatchToProps = (dispatch: any, ownProps: INotifyComponentProps) => {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any, ownProps: INotifyComponentProps) => { const mapStateToProps = (state: Map<string, any>, ownProps: INotifyComponentProps) => {
return { return {
notifications: state.notify.userNotifies, notifications: state.getIn(['notify', 'userNotifies']),
info: state.user.info info: state.getIn(['user', 'info'])
} }
} }

View File

@@ -4,10 +4,15 @@ import PropTypes from 'prop-types'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { NavLink } from 'react-router-dom' import { NavLink } from 'react-router-dom'
import { push } from 'react-router-redux' import { push } from 'react-router-redux'
import SvgClose from 'material-ui-icons/Close' import SvgClose from '@material-ui/icons/Close'
import { grey } from 'material-ui/colors' import { grey } from '@material-ui/core/colors'
import { withStyles } from 'material-ui/styles' import { withStyles } from '@material-ui/core/styles'
import List, { ListItem, ListItemSecondaryAction, ListItemText } from 'material-ui/List' import ListItemText from '@material-ui/core/ListItemText'
import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
import ListItem from '@material-ui/core/ListItem'
import List from '@material-ui/core/List'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemAvatar from '@material-ui/core/ListItemAvatar'
// - Import app components // - Import app components
import UserAvatar from 'components/userAvatar' import UserAvatar from 'components/userAvatar'
@@ -15,7 +20,7 @@ import UserAvatar from 'components/userAvatar'
// - Import API // - Import API
// - Import actions // - Import actions
import * as notifyActions from 'actions/notifyActions' import * as notifyActions from 'store/actions/notifyActions'
import { INotifyItemComponentProps } from './INotifyItemComponentProps' import { INotifyItemComponentProps } from './INotifyItemComponentProps'
import { INotifyItemComponentState } from './INotifyItemComponentState' import { INotifyItemComponentState } from './INotifyItemComponentState'
@@ -167,4 +172,4 @@ const mapStateToProps = (state: any, ownProps: INotifyItemComponentProps) => {
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(NotifyItemComponent as any) as any ) export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles as any)(NotifyItemComponent as any) as any )

View File

@@ -1,15 +1,12 @@
import { Comment } from 'core/domain/comments' import { Comment } from 'core/domain/comments'
import { Post } from 'core/domain/posts/post' import { Post } from 'core/domain/posts/post'
import {Map} from 'immutable'
export interface IPostComponentProps { export interface IPostComponentProps {
/** /**
* Post object * Post object
*
* @type {Post}
* @memberof IPostComponentProps
*/ */
post: Post post: Map<string, any>
/** /**
* Owner's post avatar * Owner's post avatar
@@ -113,7 +110,7 @@ export interface IPostComponentProps {
* @type {{[commentId: string]: Comment}} * @type {{[commentId: string]: Comment}}
* @memberof ICommentGroupComponentProps * @memberof ICommentGroupComponentProps
*/ */
commentList?: {[commentId: string]: Comment} commentList?: Map<string, Comment>
/** /**
* Styles * Styles

View File

@@ -8,47 +8,55 @@ import moment from 'moment/moment'
import Linkify from 'react-linkify' import Linkify from 'react-linkify'
import copy from 'copy-to-clipboard' import copy from 'copy-to-clipboard'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import { Map } from 'immutable'
// - Material UI // - Material UI
import { Card, CardActions, CardHeader, CardMedia, CardContent } from 'material-ui' import Card from '@material-ui/core/Card'
import Typography from 'material-ui/Typography' import CardActions from '@material-ui/core/CardActions'
import SvgShare from 'material-ui-icons/Share' import CardHeader from '@material-ui/core/CardHeader'
import SvgLink from 'material-ui-icons/Link' import CardMedia from '@material-ui/core/CardMedia'
import SvgComment from 'material-ui-icons/Comment' import CardContent from '@material-ui/core/CardContent'
import SvgFavorite from 'material-ui-icons/Favorite' import LinearProgress from '@material-ui/core/LinearProgress'
import SvgFavoriteBorder from 'material-ui-icons/FavoriteBorder' import Typography from '@material-ui/core/Typography'
import Checkbox from 'material-ui/Checkbox' import SvgShare from '@material-ui/icons/Share'
import Button from 'material-ui/Button' import SvgComment from '@material-ui/icons/Comment'
import Divider from 'material-ui/Divider' import SvgFavorite from '@material-ui/icons/Favorite'
import { grey } from 'material-ui/colors' import SvgFavoriteBorder from '@material-ui/icons/FavoriteBorder'
import Paper from 'material-ui/Paper' import Checkbox from '@material-ui/core/Checkbox'
import Menu from 'material-ui/Menu' import Button from '@material-ui/core/Button'
import { MenuList, MenuItem } from 'material-ui/Menu' import Divider from '@material-ui/core/Divider'
import TextField from 'material-ui/TextField' import { grey } from '@material-ui/core/colors'
import Dialog from 'material-ui/Dialog' import Paper from '@material-ui/core/Paper'
import IconButton from 'material-ui/IconButton' import Menu from '@material-ui/core/Menu'
import MoreVertIcon from 'material-ui-icons/MoreVert' import MenuList from '@material-ui/core/MenuList'
import { ListItemIcon, ListItemText } from 'material-ui/List' import MenuItem from '@material-ui/core/MenuItem'
import { withStyles } from 'material-ui/styles' import TextField from '@material-ui/core/TextField'
import Dialog from '@material-ui/core/Dialog'
import IconButton from '@material-ui/core/IconButton'
import MoreVertIcon from '@material-ui/icons/MoreVert'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemText from '@material-ui/core/ListItemText'
import { withStyles } from '@material-ui/core/styles'
import { Manager, Target, Popper } from 'react-popper' import { Manager, Target, Popper } from 'react-popper'
import Grow from 'material-ui/transitions/Grow' import Grow from '@material-ui/core/Grow'
import ClickAwayListener from 'material-ui/utils/ClickAwayListener' import ClickAwayListener from '@material-ui/core/ClickAwayListener'
import classNames from 'classnames' import classNames from 'classnames'
import reactStringReplace from 'react-string-replace' import reactStringReplace from 'react-string-replace'
// - Import app components // - Import app components
import CommentGroup from 'components/commentGroup' import CommentGroup from 'components/commentGroup'
import ShareDialog from 'components/shareDialog'
import PostWrite from 'components/postWrite' import PostWrite from 'components/postWrite'
import Img from 'components/img' import Img from 'components/img'
import IconButtonElement from 'layouts/IconButtonElement' import IconButtonElement from 'layouts/IconButtonElement'
import UserAvatar from 'components/userAvatar' import UserAvatar from 'components/userAvatar'
// - Import actions // - Import actions
import * as voteActions from 'actions/voteActions' import * as voteActions from 'store/actions/voteActions'
import * as postActions from 'actions/postActions' import * as postActions from 'store/actions/postActions'
import * as commentActions from 'actions/commentActions' import * as commentActions from 'store/actions/commentActions'
import * as globalActions from 'actions/globalActions' import * as globalActions from 'store/actions/globalActions'
import { IPostComponentProps } from './IPostComponentProps' import { IPostComponentProps } from './IPostComponentProps'
import { IPostComponentState } from './IPostComponentState' import { IPostComponentState } from './IPostComponentState'
@@ -65,7 +73,8 @@ const styles = (theme: any) => ({
color: 'rgb(134, 129, 129)', color: 'rgb(134, 129, 129)',
fontSize: 10, fontSize: 10,
fontWeight: 400, fontWeight: 400,
padding: 2 padding: 2,
zIndex: 1
}, },
commentCounter: { commentCounter: {
color: 'rgb(134, 129, 129)', color: 'rgb(134, 129, 129)',
@@ -80,18 +89,6 @@ const styles = (theme: any) => ({
pointerEvents: 'none', pointerEvents: 'none',
zIndex: 0 zIndex: 0
}, },
shareLinkPaper: {
minHeight: 80,
padding: 10,
minWidth: 460
},
clipboard: {
fontSize: '18px',
textAlign: 'center',
marginTop: '10px',
color: '#1e882d',
fontWeight: 400
},
postBody: { postBody: {
wordWrap: 'break-word', wordWrap: 'break-word',
color: 'rgba(0, 0, 0, 0.87)', color: 'rgba(0, 0, 0, 0.87)',
@@ -103,6 +100,14 @@ const styles = (theme: any) => ({
image: { image: {
width: '100%', width: '100%',
height: 500 height: 500
},
fullPageXs: {
[theme.breakpoints.down('xs')]: {
width: '100%',
height: '100%',
margin: 0,
overflowY: 'auto'
}
} }
}) })
@@ -129,7 +134,7 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
/** /**
* Post text * Post text
*/ */
text: post.body ? post.body : '', text: post.get('body', ''),
/** /**
* It's true if whole the text post is visible * It's true if whole the text post is visible
*/ */
@@ -145,11 +150,11 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
/** /**
* If it's true comment will be disabled on post * If it's true comment will be disabled on post
*/ */
disableComments: post.disableComments!, disableComments: post.get('disableComments', false),
/** /**
* If it's true share will be disabled on post * If it's true share will be disabled on post
*/ */
disableSharing: post.disableSharing!, disableSharing: post.get('disableSharing', false),
/** /**
* Title of share post * Title of share post
*/ */
@@ -191,7 +196,8 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
*/ */
handleOpenComments = () => { handleOpenComments = () => {
const { getPostComments, commentList, post } = this.props const { getPostComments, commentList, post } = this.props
const { id, ownerUserId } = post const id = post.get('id')
const ownerUserId = post.get('ownerUserId')
if (!commentList) { if (!commentList) {
getPostComments!(ownerUserId!, id!) getPostComments!(ownerUserId!, id!)
} }
@@ -232,14 +238,13 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
*/ */
handleDelete = () => { handleDelete = () => {
const { post } = this.props const { post } = this.props
this.props.delete!(post.id!) this.props.delete!(post.get('id'))
} }
/** /**
* Open post menu * Open post menu
*/ */
openPostMenu = (event: any) => { openPostMenu = (event: any) => {
console.log(event.currentTarget)
this.setState({ this.setState({
postMenuAnchorEl: event.currentTarget, postMenuAnchorEl: event.currentTarget,
isPostMenuOpen: true isPostMenuOpen: true
@@ -278,7 +283,7 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
*/ */
handleOpenShare = () => { handleOpenShare = () => {
const {post} = this.props const {post} = this.props
copy(`${location.origin}/${post.ownerUserId}/posts/${post.id}`) copy(`${location.origin}/${post.get('ownerUserId')}/posts/${post.get('id')}`)
this.setState({ this.setState({
shareOpen: true shareOpen: true
}) })
@@ -340,7 +345,7 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
render () { render () {
const { post, setHomeTitle, goTo, fullName, isPostOwner, commentList, avatar, classes , translate} = this.props const { post, setHomeTitle, goTo, fullName, isPostOwner, commentList, avatar, classes , translate} = this.props
const { postMenuAnchorEl, isPostMenuOpen } = this.state const { postMenuAnchorEl, isPostMenuOpen } = this.state
const RightIconMenu = () => ( const rightIconMenu = (
<Manager> <Manager>
<Target> <Target>
<IconButton <IconButton
@@ -355,21 +360,21 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
<Popper <Popper
placement='bottom-start' placement='bottom-start'
eventsEnabled={isPostMenuOpen!} eventsEnabled={isPostMenuOpen!}
className={classNames({ [classes.popperClose]: !isPostMenuOpen! }, { [classes.popperOpen]: isPostMenuOpen! })} className={classNames({ [classes.popperClose]: !isPostMenuOpen }, { [classes.popperOpen]: isPostMenuOpen })}
> >
<ClickAwayListener onClickAway={this.closePostMenu}> <ClickAwayListener onClickAway={this.closePostMenu}>
<Grow in={isPostMenuOpen!} > <Grow in={isPostMenuOpen} >
<Paper> <Paper>
<MenuList role='menu'> <MenuList role='menu'>
<MenuItem onClick={this.handleOpenPostWrite} > {translate!('post.edit')} </MenuItem> <MenuItem onClick={this.handleOpenPostWrite} > {translate!('post.edit')} </MenuItem>
<MenuItem onClick={this.handleDelete} > {translate!('post.delete')} </MenuItem> <MenuItem onClick={this.handleDelete} > {translate!('post.delete')} </MenuItem>
<MenuItem <MenuItem
onClick={() => this.props.toggleDisableComments!(!post.disableComments)} > onClick={() => this.props.toggleDisableComments!(!post.get('disableComments'))} >
{post.disableComments ? translate!('post.enableComments') : translate!('post.disableComments')} {post.get('disableComments') ? translate!('post.enableComments') : translate!('post.disableComments')}
</MenuItem> </MenuItem>
<MenuItem <MenuItem
onClick={() => this.props.toggleSharingComments!(!post.disableSharing)} > onClick={() => this.props.toggleSharingComments!(!post.get('disableSharing'))} >
{post.disableSharing ? translate!('post.enableSharing') : translate!('post.disableSharing')} {post.get('disableSharing') ? translate!('post.enableSharing') : translate!('post.disableSharing')}
</MenuItem> </MenuItem>
</MenuList> </MenuList>
</Paper> </Paper>
@@ -379,15 +384,25 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
</Manager> </Manager>
) )
const { ownerUserId, ownerDisplayName, creationDate, image, body } = post const {
ownerUserId,
ownerDisplayName,
creationDate,
image,
body,
id,
disableComments,
commentCounter,
disableSharing ,
} = post.toJS()
// Define variables // Define variables
return ( return (
<Card> <Card key={`post-component-${id}`}>
<CardHeader <CardHeader
title={<NavLink to={`/${ownerUserId}`}>{ownerDisplayName}</NavLink>} title={<NavLink to={`/${ownerUserId}`}>{ownerDisplayName}</NavLink>}
subheader={moment.unix(creationDate!).fromNow() + ' | ' + translate!('post.public')} subheader={creationDate ? moment.unix(creationDate!).fromNow() + ' | ' + translate!('post.public') : <LinearProgress color='primary' />}
avatar={<NavLink to={`/${ownerUserId}`}><UserAvatar fullName={fullName!} fileName={avatar!} size={36} /></NavLink>} avatar={<NavLink to={`/${ownerUserId}`}><UserAvatar fullName={fullName!} fileName={avatar!} size={36} /></NavLink>}
action={isPostOwner ? <RightIconMenu /> : ''} action={isPostOwner ? rightIconMenu : ''}
> >
</CardHeader> </CardHeader>
{image ? ( {image ? (
@@ -430,16 +445,16 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
<div className={classes.voteCounter}> {this.props.voteCount! > 0 ? this.props.voteCount : ''} </div> <div className={classes.voteCounter}> {this.props.voteCount! > 0 ? this.props.voteCount : ''} </div>
</IconButton> </IconButton>
</div> </div>
{!post.disableComments ? {!disableComments ?
(<div style={{ display: 'inherit' }}><IconButton (<div style={{ display: 'inherit' }}><IconButton
className={classes.iconButton} className={classes.iconButton}
onClick={this.handleOpenComments} onClick={this.handleOpenComments}
aria-label='Comment'> aria-label='Comment'>
<SvgComment /> <SvgComment />
<div className={classes.commentCounter}>{post.commentCounter! > 0 ? post.commentCounter : ''} </div> <div className={classes.commentCounter}>{commentCounter! > 0 ? commentCounter : ''} </div>
</IconButton> </IconButton>
</div>) : ''} </div>) : ''}
{!post.disableSharing ? (<IconButton {!disableSharing ? (<IconButton
className={classes.iconButton} className={classes.iconButton}
onClick={this.handleOpenShare} onClick={this.handleOpenShare}
aria-label='Comment'> aria-label='Comment'>
@@ -448,33 +463,17 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
</CardActions> </CardActions>
<CommentGroup open={this.state.openComments} comments={commentList} ownerPostUserId={post.ownerUserId!} onToggleRequest={this.handleOpenComments} isPostOwner={this.props.isPostOwner!} disableComments={post.disableComments!} postId={post.id!} /> <CommentGroup open={this.state.openComments} comments={commentList} ownerPostUserId={ownerUserId!} onToggleRequest={this.handleOpenComments} isPostOwner={this.props.isPostOwner!} disableComments={disableComments!} postId={id} />
{/* Copy link dialog*/} <ShareDialog
<Dialog onClose={this.handleCloseShare}
title='Share On' shareOpen={this.state.shareOpen}
open={this.state.shareOpen} onCopyLink={this.handleCopyLink}
onClose={this.handleCloseShare} openCopyLink={this.state.openCopyLink}
> post={post}
<Paper className={classes.shareLinkPaper}>
{!this.state.openCopyLink
? (<MenuList>
<MenuItem onClick={this.handleCopyLink} >
<ListItemIcon>
<SvgLink />
</ListItemIcon>
<ListItemText inset primary={translate!('post.copyLinkButton')} />
</MenuItem>
</MenuList>)
: <div>
<TextField autoFocus fullWidth={true} id='text-field-default' defaultValue={`${location.origin}/${post.ownerUserId}/posts/${post.id}`} />
<Typography className={classNames('animate-top', classes.clipboard)} variant='headline' component='h2'>
Link has been copied to clipboard ...
</Typography>
</div>}
</Paper>
</Dialog>
/>
<PostWrite <PostWrite
open={this.state.openPostWrite} open={this.state.openPostWrite}
onRequestClose={this.handleClosePostWrite} onRequestClose={this.handleClosePostWrite}
@@ -497,20 +496,18 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
const mapDispatchToProps = (dispatch: any, ownProps: IPostComponentProps) => { const mapDispatchToProps = (dispatch: any, ownProps: IPostComponentProps) => {
const { post } = ownProps const { post } = ownProps
return { return {
vote: () => dispatch(voteActions.dbAddVote(post.id!, post.ownerUserId!)), vote: () => dispatch(voteActions.dbAddVote(post.get('id'), post.get('ownerUserId'))),
unvote: () => dispatch(voteActions.dbDeleteVote(post.id!, post.ownerUserId!)), unvote: () => dispatch(voteActions.dbDeleteVote(post.get('id'), post.get('ownerUserId'))),
delete: (id: string) => dispatch(postActions.dbDeletePost(id)), delete: (id: string) => dispatch(postActions.dbDeletePost(id)),
toggleDisableComments: (status: boolean) => { toggleDisableComments: (status: boolean) => {
post.disableComments = status dispatch(postActions.dbUpdatePost(post.set('disableComments', status), (x: any) => x))
dispatch(postActions.dbUpdatePost(post, (x: any) => x))
}, },
toggleSharingComments: (status: boolean) => { toggleSharingComments: (status: boolean) => {
post.disableSharing = status dispatch(postActions.dbUpdatePost(post.set('disableSharing', status), (x: any) => x))
dispatch(postActions.dbUpdatePost(post, (x: any) => x))
}, },
goTo: (url: string) => dispatch(push(url)), goTo: (url: string) => dispatch(push(url)),
setHomeTitle: (title: string) => dispatch(globalActions.setHeaderTitle(title || '')), setHomeTitle: (title: string) => dispatch(globalActions.setHeaderTitle(title || '')),
getPostComments: (ownerUserId: string, postId: string) => dispatch(commentActions.dbGetComments(ownerUserId, postId)) getPostComments: (ownerUserId: string, postId: string) => dispatch(commentActions.dbFetchComments(ownerUserId, postId))
} }
} }
@@ -521,21 +518,21 @@ const mapDispatchToProps = (dispatch: any, ownProps: IPostComponentProps) => {
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any, ownProps: IPostComponentProps) => { const mapStateToProps = (state: Map<string, any>, ownProps: IPostComponentProps) => {
const { post, vote, authorize, comment } = state
const { uid } = authorize const uid = state.getIn(['authorize', 'uid'])
let currentUserVote = ownProps.post.votes ? ownProps.post.votes[uid] : false let currentUserVote = ownProps.post.getIn(['votes', uid], false)
const postModel = post.userPosts[ownProps.post.ownerUserId!][ownProps.post.id!] const voteCount = state.getIn(['post', 'userPosts', ownProps.post.get('ownerUserId'), ownProps.post.get('id'), 'score'], 0)
const postOwner = (post.userPosts[uid] ? Object.keys(post.userPosts[uid]).filter((key) => { return ownProps.post.id === key }).length : 0) const commentList: { [commentId: string]: Comment } = state.getIn(['comment', 'postComments', ownProps.post.get('id')])
const commentList: { [commentId: string]: Comment } = comment.postComments[ownProps.post.id!] const user = state.getIn(['user', 'info', ownProps.post.get('ownerUserId')])
return { return {
translate: getTranslate(state.locale), translate: getTranslate(state.get('locale')),
commentList, commentList,
avatar: state.user.info && state.user.info[ownProps.post.ownerUserId!] ? state.user.info[ownProps.post.ownerUserId!].avatar || '' : '', avatar: user ? user.avatar : '',
fullName: state.user.info && state.user.info[ownProps.post.ownerUserId!] ? state.user.info[ownProps.post.ownerUserId!].fullName || '' : '', fullName: user ? user.fullName : '',
voteCount: postModel.score, voteCount,
currentUserVote, currentUserVote,
isPostOwner: postOwner > 0 isPostOwner: uid === ownProps.post.get('ownerUserId')
} }
} }

View File

@@ -1,5 +1,5 @@
import { Post } from 'core/domain/posts' import { Post } from 'core/domain/posts'
import {Map} from 'immutable'
export interface IPostWriteComponentProps { export interface IPostWriteComponentProps {
/** /**
@@ -55,7 +55,7 @@ export interface IPostWriteComponentProps {
/** /**
* Post model * Post model
*/ */
postModel?: Post postModel?: Map<string, any>
/** /**
* Save a post * Save a post
@@ -69,7 +69,7 @@ export interface IPostWriteComponentProps {
* *
* @memberof IPostWriteComponentProps * @memberof IPostWriteComponentProps
*/ */
update?: (post: Post, callback: Function) => any update?: (post: Map<string, any>, callback: Function) => any
/** /**
* Styles * Styles

View File

@@ -3,36 +3,36 @@ import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import { Map } from 'immutable'
import { Card, CardActions, CardHeader, CardMedia, CardContent } from 'material-ui' import { Card, CardActions, CardHeader, CardMedia, CardContent } from '@material-ui/core'
import List, { import ListItemText from '@material-ui/core/ListItemText'
ListItem, import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
ListItemAvatar, import ListItem from '@material-ui/core/ListItem'
ListItemIcon, import List from '@material-ui/core/List'
ListItemSecondaryAction, import ListItemIcon from '@material-ui/core/ListItemIcon'
ListItemText import ListItemAvatar from '@material-ui/core/ListItemAvatar'
} from 'material-ui/List' import Paper from '@material-ui/core/Paper'
import Paper from 'material-ui/Paper' import Dialog from '@material-ui/core/Dialog'
import Dialog, { import DialogActions from '@material-ui/core/DialogActions'
DialogActions, import DialogContent from '@material-ui/core/DialogContent'
DialogContent, import DialogTitle from '@material-ui/core/DialogTitle'
DialogContentText, import DialogContentText from '@material-ui/core/DialogContentText'
DialogTitle import Button from '@material-ui/core/Button'
} from 'material-ui/Dialog' import RaisedButton from '@material-ui/core/Button'
import Button from 'material-ui/Button' import { grey } from '@material-ui/core/colors'
import RaisedButton from 'material-ui/Button' import IconButton from '@material-ui/core/IconButton'
import { grey } from 'material-ui/colors' import TextField from '@material-ui/core/TextField'
import IconButton from 'material-ui/IconButton' import Tooltip from '@material-ui/core/Tooltip'
import TextField from 'material-ui/TextField' import MenuList from '@material-ui/core/MenuList'
import Tooltip from 'material-ui/Tooltip' import MenuItem from '@material-ui/core/MenuItem'
import { MenuList, MenuItem } from 'material-ui/Menu' import SvgRemoveImage from '@material-ui/icons/RemoveCircle'
import SvgRemoveImage from 'material-ui-icons/RemoveCircle' import SvgCamera from '@material-ui/icons/PhotoCamera'
import SvgCamera from 'material-ui-icons/PhotoCamera' import MoreVertIcon from '@material-ui/icons/MoreVert'
import MoreVertIcon from 'material-ui-icons/MoreVert' import { withStyles } from '@material-ui/core/styles'
import { withStyles } from 'material-ui/styles'
import { Manager, Target, Popper } from 'react-popper' import { Manager, Target, Popper } from 'react-popper'
import Grow from 'material-ui/transitions/Grow' import Grow from '@material-ui/core/Grow'
import ClickAwayListener from 'material-ui/utils/ClickAwayListener' import ClickAwayListener from '@material-ui/core/ClickAwayListener'
import classNames from 'classnames' import classNames from 'classnames'
// - Import app components // - Import app components
@@ -44,13 +44,22 @@ import UserAvatarComponent from 'components/userAvatar'
import * as PostAPI from 'api/PostAPI' import * as PostAPI from 'api/PostAPI'
// - Import actions // - Import actions
import * as imageGalleryActions from 'actions/imageGalleryActions' import * as imageGalleryActions from 'store/actions/imageGalleryActions'
import * as postActions from 'actions/postActions' import * as postActions from 'store/actions/postActions'
import { IPostWriteComponentProps } from './IPostWriteComponentProps' import { IPostWriteComponentProps } from './IPostWriteComponentProps'
import { IPostWriteComponentState } from './IPostWriteComponentState' import { IPostWriteComponentState } from './IPostWriteComponentState'
import { Post } from 'core/domain/posts' import { Post } from 'core/domain/posts'
import Grid from '@material-ui/core/Grid/Grid'
const styles = (theme: any) => ({ const styles = (theme: any) => ({
fullPageXs: {
[theme.breakpoints.down('xs')]: {
width: '100%',
height: '100%',
margin: 0,
overflowY: 'auto'
}
},
backdrop: { backdrop: {
top: 0, top: 0,
left: 0, left: 0,
@@ -62,7 +71,7 @@ const styles = (theme: any) => ({
backgroundColor: 'rgba(251, 249, 249, 0.5)', backgroundColor: 'rgba(251, 249, 249, 0.5)',
WebkitTapHighlightColor: 'transparent' WebkitTapHighlightColor: 'transparent'
}, },
root: { content: {
padding: 0, padding: 0,
paddingTop: 0 paddingTop: 0
}, },
@@ -88,7 +97,7 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
* Component constructor * Component constructor
* @param {object} props is an object properties of component * @param {object} props is an object properties of component
*/ */
constructor (props: IPostWriteComponentProps) { constructor(props: IPostWriteComponentProps) {
super(props) super(props)
@@ -99,15 +108,15 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
/** /**
* Post text * Post text
*/ */
postText: this.props.edit && postModel ? (postModel.body ? postModel.body! : '') : '', postText: this.props.edit && postModel ? postModel.get('body', '') : '',
/** /**
* The URL image of the post * The URL image of the post
*/ */
image: this.props.edit && postModel ? (postModel.image ? postModel.image! : '') : '', image: this.props.edit && postModel ? postModel.get('image', '') : '',
/** /**
* The path identifier of image on the server * The path identifier of image on the server
*/ */
imageFullPath: this.props.edit && postModel ? (postModel.imageFullPath ? postModel.imageFullPath! : '') : '', imageFullPath: this.props.edit && postModel ? postModel.get('imageFullPath', '') : '',
/** /**
* If it's true gallery will be open * If it's true gallery will be open
*/ */
@@ -123,11 +132,11 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
/** /**
* If it's true comment will be disabled on post * If it's true comment will be disabled on post
*/ */
disableComments: this.props.edit && postModel ? postModel.disableComments! : false, disableComments: this.props.edit && postModel ? postModel.get('disableComments') : false,
/** /**
* If it's true share will be disabled on post * If it's true share will be disabled on post
*/ */
disableSharing: this.props.edit && postModel ? postModel.disableSharing! : false disableSharing: this.props.edit && postModel ? postModel.get('disableSharing') : false
} }
@@ -244,14 +253,14 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
}, onRequestClose) }, onRequestClose)
} }
} else { // In edit status we pass post to update functions } else { // In edit status we pass post to update functions
postModel!.body = postText const updatedPost = postModel!.set('body', postText)
postModel!.tags = tags .set('tags', tags)
postModel!.image = image .set('image', image)
postModel!.imageFullPath = imageFullPath .set('imageFullPath', imageFullPath)
postModel!.disableComments = disableComments .set('disableComments', disableComments)
postModel!.disableSharing = disableSharing .set('disableSharing', disableSharing)
update!(postModel!, onRequestClose) update!(updatedPost, onRequestClose)
} }
} }
@@ -324,38 +333,42 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
}) })
} }
componentWillReceiveProps (nextProps: IPostWriteComponentProps) { componentWillReceiveProps(nextProps: IPostWriteComponentProps) {
if (!nextProps.open) { if (!nextProps.open) {
const { postModel } = this.props const { postModel } = this.props
this.setState({ this.setState({
/** /**
* Post text * Post text
*/ */
postText: this.props.edit && postModel ? (postModel.body ? postModel.body! : '') : '', postText: this.props.edit && postModel ? postModel.get('body', '') : '',
/** /**
* The URL image of the post * The URL image of the post
*/ */
image: this.props.edit && postModel ? (postModel.image ? postModel.image! : '') : '', image: this.props.edit && postModel ? postModel.get('image', '') : '',
/** /**
* The path identifier of image on the server * The path identifier of image on the server
*/ */
imageFullPath: this.props.edit && postModel ? (postModel.imageFullPath ? postModel.imageFullPath! : '') : '', imageFullPath: this.props.edit && postModel ? postModel.get('imageFullPath', '') : '',
/** /**
* If it's true gallery will be open * If it's true gallery will be open
*/ */
galleryOpen: false, galleryOpen: false,
/** /**
* If it's true post button will be disabled * Whether menu is open
*/ */
disabledPost: true, menuOpen: false,
/** /**
* If it's true comment will be disabled on post * If it's true post button will be disabled
*/ */
disableComments: this.props.edit && postModel ? postModel.disableComments! : false, disabledPost: true,
/** /**
* If it's true share will be disabled on post * If it's true comment will be disabled on post
*/ */
disableSharing: this.props.edit && postModel ? postModel.disableSharing! : false disableComments: this.props.edit && postModel ? postModel.get('disableComments') : false,
/**
* If it's true share will be disabled on post
*/
disableSharing: this.props.edit && postModel ? postModel.get('disableSharing') : false
}) })
} }
@@ -365,7 +378,7 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
* Reneder component DOM * Reneder component DOM
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render () { render() {
const { classes, translate } = this.props const { classes, translate } = this.props
const { menuOpen } = this.state const { menuOpen } = this.state
@@ -423,28 +436,28 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
*/ */
const loadImage = (this.state.image && this.state.image !== '') const loadImage = (this.state.image && this.state.image !== '')
? ( ? (
<div> <div>
<div style={{ position: 'relative', overflowY: 'hidden', overflowX: 'auto' }}> <div style={{ position: 'relative', overflowY: 'hidden', overflowX: 'auto' }}>
<ul style={{ position: 'relative', whiteSpace: 'nowrap', padding: '0 0 0 16px', margin: '8px 0 0 0', paddingRight: '16px', verticalAlign: 'bottom', flexShrink: 0, listStyleType: 'none' }}> <ul style={{ position: 'relative', whiteSpace: 'nowrap', padding: '0 0 0 16px', margin: '8px 0 0 0', paddingRight: '16px', verticalAlign: 'bottom', flexShrink: 0, listStyleType: 'none' }}>
<div style={{ display: 'flex', position: 'relative' }}> <div style={{ display: 'flex', position: 'relative' }}>
<span onClick={this.handleRemoveImage} style={{ <span onClick={this.handleRemoveImage} style={{
position: 'absolute', width: '28px', backgroundColor: 'rgba(255, 255, 255, 0.22)', position: 'absolute', width: '28px', backgroundColor: 'rgba(255, 255, 255, 0.22)',
height: '28px', right: 12, top: 4, cursor: 'pointer', borderRadius: '50%', height: '28px', right: 12, top: 4, cursor: 'pointer', borderRadius: '50%',
display: 'flex', alignItems: 'center', justifyContent: 'center' display: 'flex', alignItems: 'center', justifyContent: 'center'
}}> }}>
<SvgRemoveImage style={{ color: 'rgba(0, 0, 0, 0.53)' }} /> <SvgRemoveImage style={{ color: 'rgba(0, 0, 0, 0.53)' }} />
</span> </span>
<div style={{ display: 'inline-block', width: '100%', marginRight: '8px', transition: 'transform .25s' }}> <div style={{ display: 'inline-block', width: '100%', marginRight: '8px', transition: 'transform .25s' }}>
<li style={{ width: '100%', margin: 0, verticalAlign: 'bottom', position: 'static' }}> <li style={{ width: '100%', margin: 0, verticalAlign: 'bottom', position: 'static' }}>
<Img fileName={this.state.image} style={{ width: '100%', height: 'auto' }} /> <Img fileName={this.state.image} style={{ width: '100%', height: 'auto' }} />
</li> </li>
</div>
</div> </div>
</div>
</ul> </ul>
</div>
</div> </div>
</div>
) : '' ) : ''
const styles = { const styles = {
@@ -459,14 +472,15 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
<div style={this.props.style}> <div style={this.props.style}>
{this.props.children} {this.props.children}
<Dialog <Dialog
BackdropProps={{className: classes.backdrop} as any} BackdropProps={{ className: classes.backdrop } as any}
PaperProps={{className: classes.fullPageXs}}
key={this.props.id || 0} key={this.props.id || 0}
open={this.props.open} open={this.props.open}
onClose={this.props.onRequestClose} onClose={this.props.onRequestClose}
> >
<DialogContent <DialogContent
className={classes.root} className={classes.content}
style={{paddingTop: 0}} style={{ paddingTop: 0 }}
> >
@@ -478,10 +492,11 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
> >
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<Grid item xs={12}>
<div style={{ display: 'flex', flexDirection: 'column', flexGrow: 1, overflow: 'hidden' }}> <div style={{ display: 'flex', flexDirection: 'column', flexGrow: 1, overflow: 'hidden' }}>
<div style={{ position: 'relative', flexDirection: 'column', display: 'flex', flexGrow: 1, overflow: 'hidden', overflowY: 'auto', maxHeight: '300px' }}> <div style={{ position: 'relative', flexDirection: 'column', display: 'flex', flexGrow: 1, overflow: 'hidden', overflowY: 'auto', maxHeight: '300px' }}>
<TextField <TextField
autoFocus autoFocus
value={this.state.postText} value={this.state.postText}
onChange={this.handleOnChange} onChange={this.handleOnChange}
placeholder={translate!('post.textareaPlaceholder')} placeholder={translate!('post.textareaPlaceholder')}
@@ -504,6 +519,7 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
</div> </div>
</div> </div>
</div> </div>
</Grid>
</CardContent> </CardContent>
</Card> </Card>
</DialogContent> </DialogContent>
@@ -515,8 +531,8 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
onClick={this.props.onRequestClose} onClick={this.props.onRequestClose}
style={{ color: grey[800] }} style={{ color: grey[800] }}
> >
{translate!('post.cancelButton')} {translate!('post.cancelButton')}
</Button> </Button>
<Button <Button
color='primary' color='primary'
disableFocusRipple={true} disableFocusRipple={true}
@@ -529,6 +545,7 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
</DialogActions> </DialogActions>
</Dialog> </Dialog>
<Dialog <Dialog
PaperProps={{className: classes.fullPageXs}}
open={this.state.galleryOpen} open={this.state.galleryOpen}
onClose={this.handleCloseGallery} onClose={this.handleCloseGallery}
@@ -545,11 +562,11 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
style={{ color: grey[800] }} style={{ color: grey[800] }}
> >
{translate!('post.cancelButton')} {translate!('post.cancelButton')}
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
</div> </div >
) )
} }
} }
@@ -563,7 +580,7 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
const mapDispatchToProps = (dispatch: any, ownProps: IPostWriteComponentProps) => { const mapDispatchToProps = (dispatch: any, ownProps: IPostWriteComponentProps) => {
return { return {
post: (post: Post, callBack: Function) => dispatch(postActions.dbAddImagePost(post, callBack)), post: (post: Post, callBack: Function) => dispatch(postActions.dbAddImagePost(post, callBack)),
update: (post: Post, callBack: Function) => dispatch(postActions.dbUpdatePost(post, callBack)) update: (post: Map<string, any>, callBack: Function) => dispatch(postActions.dbUpdatePost(post, callBack))
} }
} }
@@ -573,12 +590,14 @@ const mapDispatchToProps = (dispatch: any, ownProps: IPostWriteComponentProps) =
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any, ownProps: IPostWriteComponentProps) => { const mapStateToProps = (state: Map<string, any>, ownProps: IPostWriteComponentProps) => {
const uid = state.getIn(['authorize', 'uid'])
const user = state.getIn(['user', 'info', uid], {})
return { return {
translate: getTranslate(state.locale), translate: getTranslate(state.get('locale')),
postImageState: state.imageGallery.status, postImageState: state.getIn(['imageGallery', 'status']),
ownerAvatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : '', ownerAvatar: user.avatar || '',
ownerDisplayName: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].fullName : '' ownerDisplayName: user.fullName || ''
} }
} }

View File

@@ -63,6 +63,11 @@ export interface IProfileHeaderComponentProps {
*/ */
userId: string userId: string
/**
* Whether edit profile is open
*/
editProfileOpen?: boolean
/** /**
* Translate to locale string * Translate to locale string
*/ */

View File

@@ -1,16 +1,18 @@
// - Import react components // - Import react components
import React, { Component } from 'react' import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import PropTypes from 'prop-types'
import config from 'src/config' import config from 'src/config'
import {Map} from 'immutable'
// - Material UI // - Material UI
import { grey } from 'material-ui/colors' import { grey } from '@material-ui/core/colors'
import IconButton from 'material-ui/IconButton' import IconButton from '@material-ui/core/IconButton'
import MoreVertIcon from 'material-ui-icons/MoreVert' import MoreVertIcon from '@material-ui/icons/MoreVert'
import { MenuList, MenuItem } from 'material-ui/Menu' import MenuList from '@material-ui/core/MenuList'
import Button from 'material-ui/Button' import MenuItem from '@material-ui/core/MenuItem'
import RaisedButton from 'material-ui/Button' import Menu from '@material-ui/core/Menu'
import Button from '@material-ui/core/Button'
import RaisedButton from '@material-ui/core/Button'
import EventListener, { withOptions } from 'react-event-listener' import EventListener, { withOptions } from 'react-event-listener'
import { Parallax, Background } from 'react-parallax' import { Parallax, Background } from 'react-parallax'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
@@ -23,8 +25,8 @@ import UserAvatar from 'components/userAvatar'
// - Import API // - Import API
// - Import actions // - Import actions
import * as globalActions from 'actions/globalActions' import * as globalActions from 'store/actions/globalActions'
import * as userActions from 'actions/userActions' import * as userActions from 'store/actions/userActions'
import { IProfileHeaderComponentProps } from './IProfileHeaderComponentProps' import { IProfileHeaderComponentProps } from './IProfileHeaderComponentProps'
import { IProfileHeaderComponentState } from './IProfileHeaderComponentState' import { IProfileHeaderComponentState } from './IProfileHeaderComponentState'
@@ -33,39 +35,6 @@ import { IProfileHeaderComponentState } from './IProfileHeaderComponentState'
*/ */
export class ProfileHeaderComponent extends Component<IProfileHeaderComponentProps, IProfileHeaderComponentState> { export class ProfileHeaderComponent extends Component<IProfileHeaderComponentProps, IProfileHeaderComponentState> {
static propTypes = {
/**
* User avatar address
*/
avatar: PropTypes.string,
/**
* User banner address
*/
banner: PropTypes.string,
/**
* User tagline
*/
tagLine: PropTypes.string,
/**
* User full name
*/
fullName: PropTypes.string.isRequired,
/**
* The number of followers
*/
followerCount: PropTypes.number,
/**
* User identifier
*/
userId: PropTypes.string,
/**
* If the user profile identifier of param is equal to the user authed identifier
*/
isAuthedUser: PropTypes.bool
}
/** /**
* Component constructor * Component constructor
* @param {object} props is an object properties of component * @param {object} props is an object properties of component
@@ -117,7 +86,7 @@ export class ProfileHeaderComponent extends Component<IProfileHeaderComponentPro
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render () { render () {
const {translate, isAuthedUser} = this.props const {translate, isAuthedUser, editProfileOpen} = this.props
const styles = { const styles = {
avatar: { avatar: {
border: '2px solid rgb(255, 255, 255)' border: '2px solid rgb(255, 255, 255)'
@@ -186,7 +155,7 @@ export class ProfileHeaderComponent extends Component<IProfileHeaderComponentPro
/> />
<div className='left'> <div className='left'>
{/* User avatar*/} {/* User avatar*/}
<div style={{ display: 'flex', justifyContent: 'center' }}><UserAvatar fullName={this.props.fullName} fileName={this.props.avatar} size={60} style={styles.avatar} /></div> <div style={{ display: 'flex', justifyContent: 'center' }}><UserAvatar fullName={this.props.fullName || ' '} fileName={this.props.avatar} size={60} style={styles.avatar} /></div>
<div className='info'> <div className='info'>
<div className='fullName'> <div className='fullName'>
{this.props.fullName} {this.props.fullName}
@@ -207,7 +176,7 @@ export class ProfileHeaderComponent extends Component<IProfileHeaderComponentPro
</div>) : ''} </div>) : ''}
</div> </div>
</div> </div>
{isAuthedUser ? (<EditProfile {isAuthedUser && editProfileOpen ? (<EditProfile
avatar={this.props.avatar} avatar={this.props.avatar}
banner={this.props.banner} banner={this.props.banner}
fullName={this.props.fullName} fullName={this.props.fullName}
@@ -235,10 +204,11 @@ const mapDispatchToProps = (dispatch: any, ownProps: IProfileHeaderComponentProp
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any, ownProps: IProfileHeaderComponentProps) => { const mapStateToProps = (state: Map<string, any>, ownProps: IProfileHeaderComponentProps) => {
return { return {
translate: getTranslate(state.locale) translate: getTranslate(state.get('locale')),
editProfileOpen: state.getIn(['user', 'openEditProfile'])
} }
} }

View File

@@ -1,6 +1,7 @@
import { Feed } from 'core/domain/common/feed' import { Feed } from 'core/domain/common/feed'
import { ServerRequestModel } from 'models/server/serverRequestModel' import { ServerRequestModel } from 'models/server/serverRequestModel'
import { Profile } from 'core/domain/users' import { Profile } from 'core/domain/users'
import { ServerRequestStatusType } from 'store/actions/serverRequestStatusType'
export interface ISendFeedbackComponentProps { export interface ISendFeedbackComponentProps {
/** /**
@@ -21,13 +22,18 @@ export interface ISendFeedbackComponentProps {
/** /**
* The server request of send feedback * The server request of send feedback
*/ */
sendFeedbackRequest?: ServerRequestModel sendFeedbackRequestType?: ServerRequestStatusType
/** /**
* Current user profile * Current user profile
*/ */
currentUser?: Profile currentUser?: Profile
/**
* Styles
*/
classes?: any
/** /**
* Translate to locale string * Translate to locale string
*/ */

View File

@@ -2,22 +2,27 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import Paper from 'material-ui/Paper' import classNames from 'classnames'
import TextField from 'material-ui/TextField' import {Map} from 'immutable'
import IconButton from 'material-ui/IconButton'
import SvgHappy from 'material-ui-icons/TagFaces' // - Material UI
import SvgSad from 'material-ui-icons/Face' import Paper from '@material-ui/core/Paper'
import SvgClose from 'material-ui-icons/Clear' import TextField from '@material-ui/core/TextField'
import { CircularProgress } from 'material-ui/Progress' import IconButton from '@material-ui/core/IconButton'
import Tooltip from 'material-ui/Tooltip' import SvgHappy from '@material-ui/icons/TagFaces'
import SvgSad from '@material-ui/icons/Face'
import SvgClose from '@material-ui/icons/Clear'
import CircularProgress from '@material-ui/core/CircularProgress'
import Tooltip from '@material-ui/core/Tooltip'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import { withStyles } from '@material-ui/core/styles'
// - Import app components // - Import app components
// - Import API // - Import API
// - Import actions // - Import actions
import { globalActions } from 'actions' import { globalActions } from 'store/actions'
import { Feed } from 'core/domain/common' import { Feed } from 'core/domain/common'
import { ISendFeedbackComponentProps } from './ISendFeedbackComponentProps' import { ISendFeedbackComponentProps } from './ISendFeedbackComponentProps'
@@ -28,7 +33,18 @@ import { Profile } from 'core/domain/users'
import StringAPI from 'api/StringAPI' import StringAPI from 'api/StringAPI'
import { ServerRequestType } from 'constants/serverRequestType' import { ServerRequestType } from 'constants/serverRequestType'
import { User } from 'core/domain/users' import { User } from 'core/domain/users'
import { ServerRequestStatusType } from 'actions/serverRequestStatusType' import { ServerRequestStatusType } from 'store/actions/serverRequestStatusType'
const styles = (theme: any) => ({
fullPageXs: {
[theme.breakpoints.down('xs')]: {
width: '100%',
height: '100%',
margin: 0,
overflowY: 'auto'
}
}
})
/** /**
* Create component class * Create component class
@@ -74,7 +90,7 @@ export class SendFeedbackComponent extends Component<ISendFeedbackComponentProps
} }
mainForm = () => { mainForm = () => {
const { sendFeedbackStatus, hideFeedback, sendFeed, sendFeedbackRequest, translate } = this.props const { sendFeedbackStatus, hideFeedback, sendFeed, sendFeedbackRequestType, translate } = this.props
const { feedText } = this.state const { feedText } = this.state
return ( return (
<div className='main-box'> <div className='main-box'>
@@ -134,9 +150,7 @@ export class SendFeedbackComponent extends Component<ISendFeedbackComponentProps
color='secondary' color='secondary'
size={50} size={50}
variant='determinate' variant='determinate'
value={25} value={(25 - 0) / (50 - 0) * 100}
min={0}
max={50}
/> />
</div> </div>
</div>) </div>)
@@ -153,11 +167,11 @@ export class SendFeedbackComponent extends Component<ISendFeedbackComponentProps
} }
getFeedbackForm = () => { getFeedbackForm = () => {
const { sendFeedbackStatus, hideFeedback, sendFeed, sendFeedbackRequest } = this.props const { sendFeedbackStatus, hideFeedback, sendFeed, sendFeedbackRequestType } = this.props
const { feedText } = this.state const { feedText } = this.state
if (sendFeedbackRequest) { if (sendFeedbackRequestType) {
switch (sendFeedbackRequest.status) { switch (sendFeedbackRequestType) {
case ServerRequestStatusType.Sent: case ServerRequestStatusType.Sent:
return this.loadingForm() return this.loadingForm()
@@ -180,11 +194,11 @@ export class SendFeedbackComponent extends Component<ISendFeedbackComponentProps
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render () { render () {
const { sendFeedbackStatus, hideFeedback, sendFeed, sendFeedbackRequest } = this.props const { sendFeedbackStatus, hideFeedback, sendFeed, sendFeedbackRequestType, classes } = this.props
const { feedText } = this.state const { feedText } = this.state
return ( return (
<div className='sendFeedback__content animate__up'> <div className={classNames('sendFeedback__content', 'animate__up',classes.fullPageXs)}>
<Paper className='paper' > <Paper className='paper' >
<div className='close'> <div className='close'>
<Tooltip title='Cancel' placement='bottom-start'> <Tooltip title='Cancel' placement='bottom-start'>
@@ -222,22 +236,22 @@ const mapDispatchToProps = (dispatch: Function, ownProps: ISendFeedbackComponent
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any, ownProps: ISendFeedbackComponentProps) => { const mapStateToProps = (state: Map<string, any>, ownProps: ISendFeedbackComponentProps) => {
const { server, global, authorize, user } = state const request = state.getIn(['server', 'request'])
const { request } = server const uid = state.getIn(['authorize', 'uid'])
const { uid } = authorize const requestId = StringAPI.createServerRequestId(ServerRequestType.CommonSendFeedback, uid)
const currentUser: User = user.info && user.info[uid] ? { ...user.info[uid], userId: uid } : {} const currentUser: User = { ...state.getIn(['user', 'info', uid], {}), userId: uid }
const { sendFeedbackStatus } = global const sendFeedbackStatus = state.getIn(['global', 'sendFeedbackStatus'])
const sendFeedbackRequest: ServerRequestModel = request ? request[StringAPI.createServerRequestId(ServerRequestType.CommonSendFeedback, uid)] : null const sendFeedbackRequestType = state.getIn(['server', 'request', requestId])
return { return {
translate: getTranslate(state.locale), translate: getTranslate(state.get('locale')),
sendFeedbackStatus, sendFeedbackStatus,
sendFeedbackRequest, sendFeedbackRequestType: sendFeedbackRequestType ? sendFeedbackRequestType.status : ServerRequestStatusType.NoAction,
currentUser currentUser
} }
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(SendFeedbackComponent as any) export default connect(mapStateToProps, mapDispatchToProps)((withStyles(styles as any)(SendFeedbackComponent as any)) as any)

View File

@@ -0,0 +1,41 @@
import { Post } from 'core/domain/posts'
import {Map} from 'immutable'
export interface IShareDialogComponentProps {
/**
* Whether share dialog is open
*/
shareOpen: boolean
/**
* On close share dialog
*/
onClose: () => void
/**
* Whether copy link is displayed
*/
openCopyLink: boolean
/**
* On copy link
*/
onCopyLink: () => void
/**
* The post object for sharing
*/
post: Map<string, any>
/**
* Styles
*/
classes?: any
/**
* Translate to locale string
*/
translate?: (state: any) => any
}

View File

@@ -0,0 +1,4 @@
export interface IShareDialogComponentState {
}

View File

@@ -0,0 +1,213 @@
// - Import react components
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import SvgImage from '@material-ui/icons/Image'
import { withStyles } from '@material-ui/core/styles'
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import classNames from 'classnames'
import {
FacebookShareButton,
FacebookIcon,
TwitterShareButton,
TwitterIcon,
GooglePlusShareButton,
GooglePlusIcon,
LinkedinShareButton,
LinkedinIcon
} from 'react-share'
// - Import app components
// - Import API
// - Import actions
import { IShareDialogComponentProps } from './IShareDialogComponentProps'
import { IShareDialogComponentState } from './IShareDialogComponentState'
import { Dialog, Paper, MenuList, MenuItem, ListItemIcon, ListItemText, TextField, Typography } from '@material-ui/core'
import SvgLink from '@material-ui/icons/Link'
const styles = (theme: any) => ({
image: {
verticalAlign: 'top',
maxWidth: '100%',
minWidth: '100%',
width: '100%'
},
clipboard: {
fontSize: '18px',
textAlign: 'center',
marginTop: '10px',
color: '#1e882d',
fontWeight: 400
},
networkShare: {
width: '100%',
height: '100%'
},
fullPageXs: {
[theme.breakpoints.down('xs')]: {
width: '100%',
height: '100%',
margin: 0,
overflowY: 'auto'
}
},
shareLinkPaper: {
minHeight: 80,
padding: 10,
minWidth: 460
}
})
/**
* Create component class
*/
export class ShareDialogComponent extends Component<IShareDialogComponentProps, IShareDialogComponentState> {
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props: IShareDialogComponentProps) {
super(props)
// Defaul state
this.state = {
}
// Binding functions to `this`
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
let { classes, translate, shareOpen, onClose, openCopyLink, post, onCopyLink } = this.props
return (
<Dialog
className={classes.fullPageXs}
title='Share On'
open={shareOpen}
onClose={onClose}
>
<Paper className={classes.shareLinkPaper}>
{!openCopyLink
? (<MenuList>
<div>
<FacebookShareButton
onShareWindowClose={onClose}
url={`${location.origin}/${post.get('ownerUserId')}/posts/${post.get('id')}`}
quote={post.get('body')}
hashtag={`#${post.getIn(['tags', 0], '')}`}>
<MenuItem >
<ListItemIcon classes={{ root: classes.networkShare }}>
<FacebookIcon
size={32}
round />
</ListItemIcon>
<ListItemText inset primary={translate!('post.facebookButton')} />
</MenuItem>
</FacebookShareButton>
</div>
<div>
<TwitterShareButton
onShareWindowClose={onClose}
url={`${location.origin}/${post.get('ownerUserId')}/posts/${post.get('id')}`}
quote={post.get('body')}
hashtag={`#${post.getIn(['tags', 0], '')}`}>
<MenuItem >
<ListItemIcon classes={{ root: classes.networkShare }}>
<TwitterIcon
size={32}
round />
</ListItemIcon>
<ListItemText inset primary={translate!('post.twitterButton')} />
</MenuItem>
</TwitterShareButton>
</div>
<div>
<LinkedinShareButton
onShareWindowClose={onClose}
url={`${location.origin}/${post.get('ownerUserId')}/posts/${post.get('id')}`}
quote={post.get('body')}
hashtag={`#${post.getIn(['tags', 0], '')}`}>
<MenuItem >
<ListItemIcon classes={{ root: classes.networkShare }}>
<LinkedinIcon
size={32}
round />
</ListItemIcon>
<ListItemText inset primary={translate!('post.linkedinButton')} />
</MenuItem>
</LinkedinShareButton>
</div>
<div>
<GooglePlusShareButton
onShareWindowClose={onClose}
url={`${location.origin}/${post.get('ownerUserId')}/posts/${post.get('id')}`}
quote={post.get('body')}
hashtag={`#${post.getIn(['tags', 0], '')}`}>
<MenuItem >
<ListItemIcon classes={{ root: classes.networkShare }}>
<GooglePlusIcon
size={32}
round />
</ListItemIcon>
<ListItemText inset primary={translate!('post.googlePlusButton')} />
</MenuItem>
</GooglePlusShareButton>
</div>
<MenuItem onClick={onCopyLink} >
<ListItemIcon>
<SvgLink />
</ListItemIcon>
<ListItemText inset primary={translate!('post.copyLinkButton')} />
</MenuItem>
</MenuList>)
: <div>
<TextField autoFocus fullWidth={true} id='text-field-default' defaultValue={`${location.origin}/${post.get('ownerUserId')}/posts/${post.get('id')}`} />
<Typography className={classNames('animate-top', classes.clipboard)} variant='headline' component='h2'>
Link has been copied to clipboard ...
</Typography>
</div>}
</Paper>
</Dialog>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch: any, ownProps: IShareDialogComponentProps) => {
return {
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state: any, ownProps: IShareDialogComponentProps) => {
return {
translate: getTranslate(state.get('locale'))
}
}
// - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles as any)(ShareDialogComponent as any) as any)

View File

@@ -0,0 +1,2 @@
import ShareDialogComponent from './ShareDialogComponent'
export default ShareDialogComponent

View File

@@ -10,8 +10,8 @@ import keycode from 'keycode'
// - Import API // - Import API
// - Import actions // - Import actions
import * as authorizeActions from 'actions/authorizeActions' import * as authorizeActions from 'store/actions/authorizeActions'
import * as globalActions from 'actions/globalActions' import * as globalActions from 'store/actions/globalActions'
import { ISidebarComponentProps } from './ISidebarComponentProps' import { ISidebarComponentProps } from './ISidebarComponentProps'
import { ISidebarComponentState } from './ISidebarComponentState' import { ISidebarComponentState } from './ISidebarComponentState'

View File

@@ -1,301 +0,0 @@
// - Import react components
import React,{ Component } from 'react'
import { connect } from 'react-redux'
import { push } from 'react-router-redux'
import { NavLink, withRouter } from 'react-router-dom'
import Paper from 'material-ui/Paper'
import TextField from 'material-ui/TextField'
import RaisedButton from 'material-ui/Button'
import Button from 'material-ui/Button'
import { withStyles } from 'material-ui/styles'
import config from 'src/config'
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
// - Import actions
import * as authorizeActions from 'actions/authorizeActions'
import * as globalActions from 'actions/globalActions'
// - Import app API
import StringAPI from 'api/StringAPI'
import { ISignupComponentProps } from './ISignupComponentProps'
import { ISignupComponentState } from './ISignupComponentState'
import { UserRegisterModel } from 'models/users/userRegisterModel'
const styles = (theme: any) => ({
textField: {
minWidth: 280,
marginTop: 20
}
})
// - Create Signup component class
export class SignupComponent extends Component<ISignupComponentProps,ISignupComponentState> {
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor (props: ISignupComponentProps) {
super(props)
this.state = {
fullNameInput: '',
fullNameInputError: '',
emailInput: '',
emailInputError: '',
passwordInput: '',
passwordInputError: '',
confirmInput: '',
confirmInputError: ''
}
// Binding function to `this`
this.handleForm = this.handleForm.bind(this)
}
/**
* Handle data on input change
* @param {event} evt is an event of inputs of element on change
*/
handleInputChange = (event: any) => {
const target = event.target
const value = target.type === 'checkbox' ? target.checked : target.value
const name = target.name
this.setState({
[name]: value
})
switch (name) {
case 'fullNameInput':
this.setState({
fullNameInputError: ''
})
break
case 'emailInput':
this.setState({
emailInputError: ''
})
break
case 'passwordInput':
this.setState({
confirmInputError: '',
passwordInputError: ''
})
break
case 'confirmInput':
this.setState({
confirmInputError: '',
passwordInputError: ''
})
break
case 'checkInput':
this.setState({
checkInputError: ''
})
break
default:
}
}
/**
* Handle register form
*/
handleForm = () => {
const {fullNameInput, emailInput, passwordInput, confirmInput} = this.state
const {register, translate} = this.props
let error = false
// Validate full name
let fullNameCheck = fullNameInput.trim().toLowerCase()
if (fullNameCheck.indexOf('test') > -1
|| fullNameCheck.indexOf('demo') > -1
|| fullNameCheck.indexOf('asd') > -1
|| fullNameCheck.length < 4) {
this.setState({
fullNameInputError: translate!('signup.validNameError')
})
error = true
}
/* Validate email*/
if (!StringAPI.isValidEmail(emailInput)) {
this.setState({
emailInputError: translate!('signup.validEmailError')
})
error = true
}
/* Check password */
if (passwordInput === '') {
this.setState({
passwordInputError: translate!('signup.passwordRequiredError')
})
error = true
}
if (confirmInput === '') {
this.setState({
confirmInputError: translate!('signup.confirmRequiredError')
})
error = true
} else if (confirmInput !== passwordInput) {
this.setState({
passwordInputError: translate!('signup.passwordEqualConfirmError'),
confirmInputError: translate!('signup.confirmEqualPasswordError')
})
error = true
}
if (!error) {
register!({
email: emailInput,
password: passwordInput,
fullName: fullNameInput
})
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render () {
const {classes, translate} = this.props
const paperStyle = {
minHeight: 500,
width: 450,
textAlign: 'center',
display: 'block',
margin: 'auto'
}
return (
<div>
<h1 style={{
textAlign: 'center',
padding: '20px',
fontSize: '30px',
fontWeight: 500,
lineHeight: '32px',
margin: 'auto',
color: 'rgba(138, 148, 138, 0.2)'
}}>{config.settings.appName}</h1>
<div className='animate-bottom'>
<Paper style={paperStyle} elevation={1} >
<div style={{padding: '48px 40px 36px'}}>
<div style={{paddingLeft: '40px',
paddingRight: '40px'}}>
<h2 style={{
textAlign: 'left',
paddingTop: '16px',
fontSize: '24px',
fontWeight: 400,
lineHeight: '32px',
margin: 0}} className='zoomOutLCorner animated'>{translate!('signup.title')}</h2>
</div>
<TextField
className={classes.textField}
autoFocus
onChange={this.handleInputChange}
helperText={this.state.fullNameInputError}
error={this.state.fullNameInputError.trim() !== ''}
name='fullNameInput'
label={translate!('signup.fullNameLabel')}
type='text'
/><br />
<TextField
className={classes.textField}
onChange={this.handleInputChange}
helperText={this.state.emailInputError}
error={this.state.emailInputError.trim() !== ''}
name='emailInput'
label={translate!('signup.emailLabel')}
type='email'
/><br />
<TextField
className={classes.textField}
onChange={this.handleInputChange}
helperText={this.state.passwordInputError}
error={this.state.passwordInputError.trim() !== ''}
name='passwordInput'
label={translate!('signup.passwordLabel')}
type='password'
/><br />
<TextField
className={classes.textField}
onChange={this.handleInputChange}
helperText={this.state.confirmInputError}
error={this.state.confirmInputError.trim() !== ''}
name='confirmInput'
label={translate!('signup.confirmPasswordLabel')}
type='password'
/><br />
<br />
<div className='signup__button-box'>
<div>
<Button onClick={this.props.loginPage}>{translate!('signup.loginButton')}</Button>
</div>
<div>
<Button variant='raised' color='primary' onClick={this.handleForm}>{translate!('signup.createButton')}</Button>
</div>
</div>
</div>
</Paper>
</div>
</div>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch: any,ownProps: ISignupComponentProps) => {
return {
showError: (message: string) => {
dispatch(globalActions.showMessage(message))
},
register: (userRegister: UserRegisterModel) => {
dispatch(authorizeActions.dbSignup(userRegister))
},
loginPage: () => {
dispatch(push('/login'))
}
}
}
/**
* Map state to props
* @param {object} state is the obeject from redux store
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapStateToProps = (state: any,ownProps: ISignupComponentProps) => {
return{
translate: getTranslate(state.locale),
}
}
// - Connect component to redux store
export default withRouter(connect(mapStateToProps,mapDispatchToProps)(withStyles(styles)(SignupComponent as any) as any) as any)

View File

@@ -2,14 +2,15 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import Avatar from 'material-ui/Avatar' import Avatar from '@material-ui/core/Avatar'
import { Map } from 'immutable'
// - Import app components // - Import app components
// - Import API // - Import API
// - Import actions // - Import actions
import * as imageGalleryActions from 'actions/imageGalleryActions' import * as imageGalleryActions from 'store/actions/imageGalleryActions'
import { IUserAvatarComponentProps } from './IUserAvatarComponentProps' import { IUserAvatarComponentProps } from './IUserAvatarComponentProps'
import { IUserAvatarComponentState } from './IUserAvatarComponentState' import { IUserAvatarComponentState } from './IUserAvatarComponentState'
@@ -95,8 +96,8 @@ const mapDispatchToProps = (dispatch: Function, ownProps: IUserAvatarComponentPr
*/ */
const mapStateToProps = (state: any, ownProps: IUserAvatarComponentProps) => { const mapStateToProps = (state: any, ownProps: IUserAvatarComponentProps) => {
return { return {
avatarURL: state.imageGallery.imageURLList, avatarURL: state.getIn(['imageGallery', 'imageURLList']),
imageRequests: state.imageGallery.imageRequests imageRequests: state.getIn(['imageGallery', 'imageRequests'])
} }
} }

View File

@@ -1,42 +1,30 @@
import { User } from 'core/domain/users' import { User } from 'core/domain/users'
import { Circle } from 'core/domain/circles/circle' import { Circle } from 'core/domain/circles/circle'
import { UserTie } from 'core/domain/circles' import { UserTie } from 'core/domain/circles'
import { ServerRequestStatusType } from 'actions/serverRequestStatusType' import { ServerRequestStatusType } from 'store/actions/serverRequestStatusType'
import { ServerRequestModel } from 'models/server/serverRequestModel' import { ServerRequestModel } from 'models/server/serverRequestModel'
import {Map, List} from 'immutable'
export interface IUserBoxComponentProps { export interface IUserBoxComponentProps {
/** /**
* User identifier * User identifier
*
* @type {string}
* @memberof IUserBoxComponentProps
*/ */
userId: string userId: string
/** /**
* User * User
*
* @type {User}
* @memberof IUserBoxComponentProps
*/ */
user: UserTie user: UserTie
/** /**
* Circles * Circles
*
* @type {{[circleId: string]: Circle}}
* @memberof IUserBoxComponentProps
*/ */
circles?: {[circleId: string]: Circle} circles?: Map<string, Map<string, any>>
/** /**
* List of circles' id * List of circles' id
*
* @type {string[]}
* @memberof IUserBoxComponentProps
*/ */
userBelongCircles?: string[] userBelongCircles?: List<string>
/** /**
* Whether current user followed this user * Whether current user followed this user
@@ -45,54 +33,38 @@ export interface IUserBoxComponentProps {
/** /**
* The number of circles * The number of circles
*
* @type {number}
* @memberof IUserBoxComponentProps
*/ */
belongCirclesCount?: number belongCirclesCount?: number
/** /**
* The first circle * The first circle
*
* @type {User}
* @memberof IUserBoxComponentProps
*/ */
firstBelongCircle?: Circle firstBelongCircle?: Map<string, any>
/** /**
* Avatar address * Avatar address
*
* @type {string}
* @memberof IUserBoxComponentProps
*/ */
avatar?: string avatar?: string
/** /**
* User full name * User full name
*
* @type {string}
* @memberof IUserBoxComponentProps
*/ */
fullName?: string fullName?: string
/** /**
* The `Following` circle identifier of current user * The `Following` circle identifier of current user
*/ */
followingCircleId?: string followingCircle?: Map<string, any>
/** /**
* Create a circle * Create a circle
*
* @memberof IUserBoxComponentProps
*/ */
createCircle?: (name: string) => any createCircle?: (name: string) => any
/** /**
* Add a user in a circle * Add a user in a circle
*
* @memberof IUserBoxComponentProps
*/ */
addUserToCircle?: (circleIds: string[],user: UserTie) => any addUserToCircle?: (circleIds: List<string>,user: UserTie) => any
/** /**
* Add referer user to the `Following` circle of current user * Add referer user to the `Following` circle of current user
@@ -107,12 +79,12 @@ export interface IUserBoxComponentProps {
/** /**
* Set current user selected circles for referer user * Set current user selected circles for referer user
*/ */
setSelectedCircles?: (userId: string, circleList: string[]) => any setSelectedCircles?: (userId: string, circleList: List<string>) => any
/** /**
* Remove current user selected circles for referer user * Remove current user selected circles for referer user
*/ */
removeSelectedCircles?: (userId: string, circleList: string[]) => any removeSelectedCircles?: (userId: string, circleList: List<string>) => any
/** /**
* Open select circle box * Open select circle box
@@ -126,8 +98,6 @@ export interface IUserBoxComponentProps {
/** /**
* Redirect page to [url] * Redirect page to [url]
*
* @memberof IUserBoxComponentProps
*/ */
goTo?: (url: string) => any goTo?: (url: string) => any
@@ -149,7 +119,7 @@ export interface IUserBoxComponentProps {
/** /**
* Keep selected circles for refere user * Keep selected circles for refere user
*/ */
selectedCircles?: string[] selectedCircles?: List<string>
/** /**
* Whether the select circles box for referer user is open * Whether the select circles box for referer user is open

View File

@@ -5,28 +5,33 @@ import { connect } from 'react-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { push } from 'react-router-redux' import { push } from 'react-router-redux'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import classNames from 'classnames'
import {Map, List as ImuList} from 'immutable'
// - Material UI // - Material UI
import Paper from 'material-ui/Paper' import Paper from '@material-ui/core/Paper'
import Button from 'material-ui/Button' import Button from '@material-ui/core/Button'
import RaisedButton from 'material-ui/Button' import RaisedButton from '@material-ui/core/Button'
import Menu, { MenuItem } from 'material-ui/Menu' import MenuList from '@material-ui/core/MenuList'
import Checkbox from 'material-ui/Checkbox' import MenuItem from '@material-ui/core/MenuItem'
import TextField from 'material-ui/TextField' import Checkbox from '@material-ui/core/Checkbox'
import Tooltip from 'material-ui/Tooltip' import TextField from '@material-ui/core/TextField'
import { withStyles } from 'material-ui/styles' import Tooltip from '@material-ui/core/Tooltip'
import List, { ListItem, ListItemSecondaryAction, ListItemText } from 'material-ui/List' import { withStyles } from '@material-ui/core/styles'
import Divider from 'material-ui/Divider' import ListItemText from '@material-ui/core/ListItemText'
import Dialog, { import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'
DialogActions, import ListItem from '@material-ui/core/ListItem'
DialogContent, import List from '@material-ui/core/List'
DialogContentText, import ListItemIcon from '@material-ui/core/ListItemIcon'
DialogTitle, import Divider from '@material-ui/core/Divider'
withMobileDialog import Dialog from '@material-ui/core/Dialog'
} from 'material-ui/Dialog' import DialogActions from '@material-ui/core/DialogActions'
import SvgAdd from 'material-ui-icons/Add' import DialogContent from '@material-ui/core/DialogContent'
import IconButton from 'material-ui/IconButton' import DialogTitle from '@material-ui/core/DialogTitle'
import { grey } from 'material-ui/colors' import DialogContentText from '@material-ui/core/DialogContentText'
import SvgAdd from '@material-ui/icons/Add'
import IconButton from '@material-ui/core/IconButton'
import { grey } from '@material-ui/core/colors'
// - Import app components // - Import app components
import UserAvatar from 'components/userAvatar' import UserAvatar from 'components/userAvatar'
@@ -35,14 +40,14 @@ import UserAvatar from 'components/userAvatar'
import StringAPI from 'api/StringAPI' import StringAPI from 'api/StringAPI'
// - Import actions // - Import actions
import * as circleActions from 'actions/circleActions' import * as circleActions from 'store/actions/circleActions'
import { IUserBoxComponentProps } from './IUserBoxComponentProps' import { IUserBoxComponentProps } from './IUserBoxComponentProps'
import { IUserBoxComponentState } from './IUserBoxComponentState' import { IUserBoxComponentState } from './IUserBoxComponentState'
import { User } from 'core/domain/users' import { User } from 'core/domain/users'
import { UserTie, Circle } from 'core/domain/circles' import { UserTie, Circle } from 'core/domain/circles'
import { ServerRequestType } from 'constants/serverRequestType' import { ServerRequestType } from 'constants/serverRequestType'
import { ServerRequestStatusType } from 'actions/serverRequestStatusType' import { ServerRequestStatusType } from 'store/actions/serverRequestStatusType'
import { ServerRequestModel } from 'models/server' import { ServerRequestModel } from 'models/server'
const styles = (theme: any) => ({ const styles = (theme: any) => ({
@@ -51,6 +56,14 @@ const styles = (theme: any) => ({
maxWidth: 360, maxWidth: 360,
backgroundColor: theme.palette.background.paper backgroundColor: theme.palette.background.paper
}, },
paper: {
height: 254,
width: 243,
margin: 10,
textAlign: 'center',
minWidth: 230,
maxWidth: '257px'
},
dialogContent: { dialogContent: {
paddingTop: '5px', paddingTop: '5px',
padding: '0px 5px 5px 5px' padding: '0px 5px 5px 5px'
@@ -60,6 +73,14 @@ const styles = (theme: any) => ({
}, },
space: { space: {
height: 20 height: 20
},
fullPageXs: {
[theme.breakpoints.down('xs')]: {
width: '100%',
height: '100%',
margin: 0,
overflowY: 'auto'
}
} }
}) })
@@ -83,13 +104,6 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
} }
styles = { styles = {
paper: {
height: 254,
width: 243,
margin: 10,
textAlign: 'center',
maxWidth: '257px'
},
followButton: { followButton: {
position: 'absolute', position: 'absolute',
bottom: '30px', bottom: '30px',
@@ -102,7 +116,6 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
borderRadius: '4px' borderRadius: '4px'
} }
} }
selectedCircles: string[]
/** /**
* Component constructor * Component constructor
@@ -130,7 +143,6 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
*/ */
disabledDoneCircles: true disabledDoneCircles: true
} }
this.selectedCircles = userBelongCircles!.slice()
// Binding functions to `this` // Binding functions to `this`
this.handleChangeName = this.handleChangeName.bind(this) this.handleChangeName = this.handleChangeName.bind(this)
this.onCreateCircle = this.onCreateCircle.bind(this) this.onCreateCircle = this.onCreateCircle.bind(this)
@@ -143,11 +155,10 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
* Handle follow user * Handle follow user
*/ */
handleDoneAddCircle = () => { handleDoneAddCircle = () => {
const { userId, user, addUserToCircle, selectedCircles, deleteFollowingUser } = this.props const { userId, user, addUserToCircle, selectedCircles, deleteFollowingUser, avatar, fullName } = this.props
const { avatar, fullName } = user
const { disabledDoneCircles } = this.state const { disabledDoneCircles } = this.state
if (!disabledDoneCircles) { if (!disabledDoneCircles) {
if (selectedCircles!.length > 0) { if (selectedCircles!.count() > 0) {
addUserToCircle!(selectedCircles!, { avatar, userId, fullName }) addUserToCircle!(selectedCircles!, { avatar, userId, fullName })
} else { } else {
deleteFollowingUser!(userId) deleteFollowingUser!(userId)
@@ -161,14 +172,13 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
onFollowUser = (event: any) => { onFollowUser = (event: any) => {
// This prevents ghost click // This prevents ghost click
event.preventDefault() event.preventDefault()
const { isFollowed, followUser, followingCircleId, userId, user, followRequest } = this.props const { isFollowed, followUser, followingCircle, userId, user, followRequest, avatar, fullName } = this.props
if (followRequest && followRequest.status === ServerRequestStatusType.Sent) { if (followRequest && followRequest.status === ServerRequestStatusType.Sent) {
return return
} }
const { avatar, fullName } = user
if (!isFollowed) { if (!isFollowed) {
followUser!(followingCircleId!, { avatar, userId, fullName }) followUser!(followingCircle!.get('id'), { avatar, userId, fullName })
} else { } else {
this.onRequestOpenAddCircle() this.onRequestOpenAddCircle()
} }
@@ -224,17 +234,13 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
handleSelectCircle = (event: object, isInputChecked: boolean, circleId: string) => { handleSelectCircle = (event: object, isInputChecked: boolean, circleId: string) => {
const { userBelongCircles, circles, setSelectedCircles, selectedCircles, userId } = this.props const { userBelongCircles, circles, setSelectedCircles, selectedCircles, userId } = this.props
let newSelectedCircles = selectedCircles!.slice() let newSelectedCircles = selectedCircles!
if (isInputChecked) { if (isInputChecked) {
newSelectedCircles = selectedCircles!.push(circleId)
newSelectedCircles = [
...selectedCircles!,
circleId
]
} else { } else {
const circleIndex = selectedCircles!.indexOf(circleId) const circleIndex = selectedCircles!.indexOf(circleId)
newSelectedCircles.splice(circleIndex, 1) newSelectedCircles = newSelectedCircles.remove(circleIndex)
} }
setSelectedCircles!(userId, newSelectedCircles) setSelectedCircles!(userId, newSelectedCircles)
@@ -248,39 +254,39 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
*/ */
circleList = () => { circleList = () => {
let { circles, userId, userBelongCircles, selectedCircles, classes } = this.props let { circles, userId, userBelongCircles, selectedCircles, classes } = this.props
const circleDomList: any[] = []
if (circles) { if (circles) {
const parsedDate = Object.keys(circles).map((circleId, index) => { circles.forEach((circle, circleId) => {
let isBelong = selectedCircles ? selectedCircles!.indexOf(circleId) > -1 : false let isBelong = selectedCircles ? selectedCircles!.indexOf(circleId!) > -1 : false
// Create checkbox for selected/unselected circle // Create checkbox for selected/unselected circle
return ( circleDomList.push(
<ListItem key={`${circleId}-${userId}`} dense className={classes.listItem}> <ListItem key={`${circleId}-${userId}`} dense className={classes.listItem}>
<ListItemText className={classes.circleName} primary={circles![circleId].name} /> <ListItemText className={classes.circleName} primary={circle!.get('name')} />
<ListItemSecondaryAction> <ListItemSecondaryAction>
<Checkbox <Checkbox
onChange={(event: object, isInputChecked: boolean) => this.handleSelectCircle(event, isInputChecked, circleId)} onChange={(event: object, isInputChecked: boolean) => this.handleSelectCircle(event, isInputChecked, circleId!)}
checked={isBelong} checked={isBelong}
/> />
</ListItemSecondaryAction> </ListItemSecondaryAction>
</ListItem>) </ListItem>)
}) })
return parsedDate return circleDomList
} }
} }
/** /**
* Check if the the selected circles changed * Check if the the selected circles changed
*/ */
selectedCircleChange = (selectedCircles: string[]) => { selectedCircleChange = (selectedCircles: ImuList<string>) => {
let isChanged = false let isChanged = false
const { userBelongCircles, circles } = this.props const { userBelongCircles, circles } = this.props
if (selectedCircles.length === userBelongCircles!.length) { if (selectedCircles.count() === userBelongCircles!.count()) {
for (let circleIndex: number = 0; circleIndex < selectedCircles.length; circleIndex++) { for (let circleIndex: number = 0; circleIndex < selectedCircles.count(); circleIndex++) {
const selectedCircleId = selectedCircles[circleIndex] const selectedCircleId = selectedCircles.get(circleIndex)
if (!(userBelongCircles!.indexOf(selectedCircleId) > -1)) { if (!(userBelongCircles!.indexOf(selectedCircleId) > -1)) {
isChanged = true isChanged = true
break break
@@ -298,10 +304,21 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
*/ */
render () { render () {
const { disabledDoneCircles } = this.state const { disabledDoneCircles } = this.state
const { isFollowed, followRequest, userId, isSelecteCirclesOpen, addToCircleRequest, deleteFollowingUserRequest, classes, translate } = this.props const {
isFollowed,
firstBelongCircle,
belongCirclesCount,
followRequest,
userId,
isSelecteCirclesOpen,
addToCircleRequest,
deleteFollowingUserRequest,
classes,
translate
} = this.props
return ( return (
<Paper key={userId} style={this.styles.paper} elevation={1} className='grid-cell'> <Paper key={userId} elevation={1} className={classNames('grid-cell', classes.paper)}>
<div style={{ <div style={{
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
@@ -321,7 +338,7 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
</div> </div>
<div onClick={() => this.props.goTo!(`/${this.props.userId}`)} className='people__name' style={{ cursor: 'pointer' }}> <div onClick={() => this.props.goTo!(`/${this.props.userId}`)} className='people__name' style={{ cursor: 'pointer' }}>
<div> <div>
{this.props.user.fullName} {this.props.fullName}
</div> </div>
</div> </div>
<div style={this.styles.followButton as any}> <div style={this.styles.followButton as any}>
@@ -334,11 +351,12 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
} }
> >
{!isFollowed ? translate!('userBox.followButton') {!isFollowed ? translate!('userBox.followButton')
: (this.props.belongCirclesCount! > 1 ? translate!('userBox.numberOfCircleButton', {circlesCount: this.props.belongCirclesCount}) : ((this.props.firstBelongCircle) ? this.props.firstBelongCircle.name : translate!('userBox.followButton')))} : (belongCirclesCount! > 1 ? translate!('userBox.numberOfCircleButton', {circlesCount: belongCirclesCount}) : ((firstBelongCircle) ? firstBelongCircle.get('name', 'Followed') : translate!('userBox.followButton')))}
</Button> </Button>
</div> </div>
</div> </div>
<Dialog <Dialog
PaperProps={{className: classes.fullPageXs}}
key={this.props.userId || 0} key={this.props.userId || 0}
open={isSelecteCirclesOpen === true} open={isSelecteCirclesOpen === true}
onClose={this.onRequestCloseAddCircle} onClose={this.onRequestCloseAddCircle}
@@ -403,7 +421,7 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
const mapDispatchToProps = (dispatch: Function, ownProps: IUserBoxComponentProps) => { const mapDispatchToProps = (dispatch: Function, ownProps: IUserBoxComponentProps) => {
return { return {
createCircle: (name: string) => dispatch(circleActions.dbAddCircle(name)), createCircle: (name: string) => dispatch(circleActions.dbAddCircle(name)),
addUserToCircle: (circleIds: string[], user: UserTie) => dispatch(circleActions.dbUpdateUserInCircles(circleIds, user)), addUserToCircle: (circleIds: ImuList<string>, user: UserTie) => dispatch(circleActions.dbUpdateUserInCircles(circleIds, user)),
followUser: (circleId: string, userFollowing: UserTie) => dispatch(circleActions.dbFollowUser(circleId, userFollowing)), followUser: (circleId: string, userFollowing: UserTie) => dispatch(circleActions.dbFollowUser(circleId, userFollowing)),
deleteFollowingUser: (followingId: string) => dispatch(circleActions.dbDeleteFollowingUser(followingId)), deleteFollowingUser: (followingId: string) => dispatch(circleActions.dbDeleteFollowingUser(followingId)),
setSelectedCircles: (userId: string, circleList: string[]) => dispatch(circleActions.setSelectedCircles(userId, circleList)), setSelectedCircles: (userId: string, circleList: string[]) => dispatch(circleActions.setSelectedCircles(userId, circleList)),
@@ -421,38 +439,43 @@ const mapDispatchToProps = (dispatch: Function, ownProps: IUserBoxComponentProps
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any, ownProps: IUserBoxComponentProps) => { const mapStateToProps = (state: Map<string, any>, ownProps: IUserBoxComponentProps) => {
const { circle, authorize, server } = state const uid = state.getIn(['authorize', 'uid'])
const { uid } = authorize const request = state.getIn(['server', 'request'])
const { request } = server
const circles: { [circleId: string]: Circle } = circle ? (circle.circleList || {}) : {} const circles: Map<string, Map<string, any>> = state.getIn(['circle', 'circleList'], {})
const userBelongCircles = circle ? (circle.userTies[ownProps.userId] ? circle.userTies[ownProps.userId].circleIdList : []) : [] const userBelongCircles: ImuList<any> = state.getIn(['circle', 'userTies', ownProps.userId, 'circleIdList'], ImuList())
const isFollowed = userBelongCircles.length > 0 const isFollowed = userBelongCircles.count() > 0
const followingCircleId = circles ? Object.keys(circles) const followingCircle = circles
.filter((circleId) => circles[circleId].isSystem && circles[circleId].name === `Following`)[0] : '' .filter((followingCircle) => followingCircle!.get('isSystem', false) && followingCircle!.get('name') === `Following`)
const followRequest: ServerRequestModel = request ? request[StringAPI.createServerRequestId(ServerRequestType.CircleFollowUser, ownProps.userId)] : null .toArray()[0]
const addToCircleRequest: ServerRequestModel = request ? request[StringAPI.createServerRequestId(ServerRequestType.CircleAddToCircle, ownProps.userId)] : null const followRequestId = StringAPI.createServerRequestId(ServerRequestType.CircleFollowUser, ownProps.userId)
const deleteFollowingUserRequest: ServerRequestModel = request ? request[StringAPI.createServerRequestId(ServerRequestType.CircleDeleteFollowingUser, ownProps.userId)] : null const followRequest = state.getIn(['server', 'request', followRequestId])
const selectedCircles = circle.selectedCircles ? circle.selectedCircles[ownProps.userId] : [] const addToCircleRequestId = StringAPI.createServerRequestId(ServerRequestType.CircleAddToCircle, ownProps.userId)
const isSelecteCirclesOpen = circle.openSelecteCircles ? circle.openSelecteCircles[ownProps.userId] : [] const addToCircleRequest = state.getIn(['server', 'request', addToCircleRequestId])
const deleteFollowingUserRequestId = StringAPI.createServerRequestId(ServerRequestType.CircleDeleteFollowingUser, ownProps.userId)
const deleteFollowingUserRequest = state.getIn(['server', 'request', deleteFollowingUserRequestId])
const selectedCircles = state.getIn(['circle', 'selectedCircles', ownProps.userId], [])
const isSelecteCirclesOpen = state.getIn(['circle', 'openSelecteCircles', ownProps.userId], [])
const userBox = state.getIn(['user', 'info', ownProps.userId])
return { return {
translate: getTranslate(state.locale), translate: getTranslate(state.get('locale')),
isSelecteCirclesOpen, isSelecteCirclesOpen,
isFollowed, isFollowed,
selectedCircles, selectedCircles,
circles, circles,
followingCircleId, followingCircle,
userBelongCircles, userBelongCircles,
followRequest, followRequest,
belongCirclesCount: userBelongCircles.length || 0, belongCirclesCount: userBelongCircles.count() || 0,
firstBelongCircle: userBelongCircles ? (circles ? circles[userBelongCircles[0]] : {}) : {}, firstBelongCircle: userBelongCircles ? circles.get(userBelongCircles.get(0), Map({})) : Map({}),
avatar: state.user.info && state.user.info[ownProps.userId] ? state.user.info[ownProps.userId].avatar || '' : '', avatar: userBox.avatar || '' ,
fullName: state.user.info && state.user.info[ownProps.userId] ? state.user.info[ownProps.userId].fullName || '' : '' fullName: userBox.fullName || ''
} }
} }
// - Connect component to redux store // - Connect component to redux store
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(UserBoxComponent as any) as any) export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles as any)(UserBoxComponent as any) as any)

View File

@@ -1,5 +1,6 @@
import { User } from 'core/domain/users' import { User } from 'core/domain/users'
import { UserTie } from 'core/domain/circles' import { UserTie } from 'core/domain/circles'
import {Map} from 'immutable'
export interface IUserBoxListComponentProps { export interface IUserBoxListComponentProps {
@@ -9,7 +10,7 @@ export interface IUserBoxListComponentProps {
* @type {{[userId: string]: User}} * @type {{[userId: string]: User}}
* @memberof IUserBoxListComponentProps * @memberof IUserBoxListComponentProps
*/ */
users: {[userId: string]: UserTie} users: Map<string, UserTie>
/** /**
* User identifier * User identifier

View File

@@ -2,12 +2,14 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import {Map} from 'immutable'
// - Import app components // - Import app components
import UserBox from 'components/userBox' import UserBox from 'components/userBox'
import { IUserBoxListComponentProps } from './IUserBoxListComponentProps' import { IUserBoxListComponentProps } from './IUserBoxListComponentProps'
import { IUserBoxListComponentState } from './IUserBoxListComponentState' import { IUserBoxListComponentState } from './IUserBoxListComponentState'
import { UserTie } from 'core/domain/circles/userTie'
// - Import API // - Import API
@@ -42,15 +44,17 @@ export class UserBoxListComponent extends Component<IUserBoxListComponentProps,I
} }
userList = () => { userList = () => {
let { users, uid } = this.props let { uid } = this.props
const users = this.props.users
const userBoxList: any[] = []
if (users) { if (users) {
return Object.keys(users).map((key, index) => { users.forEach((user: UserTie, key: string) => {
if (uid !== key) { if (uid !== key) {
return <UserBox key={key} userId={key} user={users[key]}/> userBoxList.push(<UserBox key={key} userId={key} user={user}/>)
} }
}) })
} }
return userBoxList
} }
/** /**
@@ -92,7 +96,7 @@ const mapDispatchToProps = (dispatch: Function, ownProps: IUserBoxListComponentP
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any, ownProps: IUserBoxListComponentProps) => { const mapStateToProps = (state: any, ownProps: IUserBoxListComponentProps) => {
const {uid} = state.authorize const uid = state.getIn(['authorize', 'uid'], 0)
return { return {
uid uid
} }

View File

@@ -1,20 +1,14 @@
import { Circle } from 'core/domain/circles' import { Circle } from 'core/domain/circles'
import {Map} from 'immutable'
export interface IYourCirclesComponentProps { export interface IYourCirclesComponentProps {
/** /**
* Circles * Circles
*
* @type {{[circleId: string]: Circle}}
* @memberof IYourCirclesComponentProps
*/ */
circles?: {[circleId: string]: Circle} circles?: Map<string, Map<string, any>>
/** /**
* User identifier * User identifier
*
* @type {string}
* @memberof IYourCirclesComponentProps
*/ */
uid?: string uid?: string
} }

View File

@@ -2,7 +2,9 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import List from 'material-ui/List' import {Map} from 'immutable'
import List from '@material-ui/core/List'
// - Import app components // - Import app components
import CircleComponent from 'components/circle' import CircleComponent from 'components/circle'
@@ -44,8 +46,8 @@ export class YourCirclesComponent extends Component<IYourCirclesComponentProps,I
let parsedCircles: any[] = [] let parsedCircles: any[] = []
if (circles) { if (circles) {
Object.keys(circles).map((key, index) => { circles.map((circle, key) => {
parsedCircles.push(<CircleComponent key={key} circle={circles![key]} id={key} uid={uid!} />) parsedCircles.push(<CircleComponent key={key} circle={circle!} id={key!} uid={uid!} />)
}) })
} }
return parsedCircles return parsedCircles
@@ -95,10 +97,9 @@ const mapDispatchToProps = (dispatch: Function, ownProps: IYourCirclesComponentP
* @param {object} ownProps is the props belong to component * @param {object} ownProps is the props belong to component
* @return {object} props of component * @return {object} props of component
*/ */
const mapStateToProps = (state: any, ownProps: IYourCirclesComponentProps) => { const mapStateToProps = (state: Map<string, any>, ownProps: IYourCirclesComponentProps) => {
const {circle, authorize, server} = state const uid = state.getIn(['authorize', 'uid'])
const { uid } = state.authorize const circles: Map<string, Map<string, any>> = state.getIn(['circle', 'circleList'], {})
const circles: { [circleId: string]: Circle } = circle ? (circle.circleList || {}) : {}
return { return {
uid, uid,
circles circles

View File

@@ -1,4 +1,4 @@
import { LanguageType } from 'reducers/locale/langugeType' import { LanguageType } from 'store/reducers/locale/langugeType'
export const environment = { export const environment = {
firebase: { firebase: {
@@ -10,6 +10,7 @@ export const environment = {
messagingSenderId: '964743099489' messagingSenderId: '964743099489'
}, },
settings: { settings: {
enabledOAuthLogin: true,
appName: 'Green', appName: 'Green',
defaultProfileCover: 'https://firebasestorage.googleapis.com/v0/b/open-social-33d92.appspot.com/o/images%2F751145a1-9488-46fd-a97e-04018665a6d3.JPG?alt=media&token=1a1d5e21-5101-450e-9054-ea4a20e06c57', defaultProfileCover: 'https://firebasestorage.googleapis.com/v0/b/open-social-33d92.appspot.com/o/images%2F751145a1-9488-46fd-a97e-04018665a6d3.JPG?alt=media&token=1a1d5e21-5101-450e-9054-ea4a20e06c57',
defaultLanguage: LanguageType.English defaultLanguage: LanguageType.English

View File

@@ -1,4 +1,4 @@
import { LanguageType } from 'reducers/locale/langugeType' import { LanguageType } from 'store/reducers/locale/langugeType'
export const environment = { export const environment = {
firebase: { firebase: {
@@ -10,6 +10,7 @@ export const environment = {
messagingSenderId: '964743099489' messagingSenderId: '964743099489'
}, },
settings: { settings: {
enabledOAuthLogin: true,
appName: 'Green', appName: 'Green',
defaultProfileCover: 'https://firebasestorage.googleapis.com/v0/b/open-social-33d92.appspot.com/o/images%2F751145a1-9488-46fd-a97e-04018665a6d3.JPG?alt=media&token=1a1d5e21-5101-450e-9054-ea4a20e06c57', defaultProfileCover: 'https://firebasestorage.googleapis.com/v0/b/open-social-33d92.appspot.com/o/images%2F751145a1-9488-46fd-a97e-04018665a6d3.JPG?alt=media&token=1a1d5e21-5101-450e-9054-ea4a20e06c57',
defaultLanguage: LanguageType.English defaultLanguage: LanguageType.English

View File

@@ -7,4 +7,5 @@ export enum CommentActionType {
UPDATE_COMMENT = 'UPDATE_COMMENT', UPDATE_COMMENT = 'UPDATE_COMMENT',
CLOSE_COMMENT_EDITOR = 'CLOSE_COMMENT_EDITOR', CLOSE_COMMENT_EDITOR = 'CLOSE_COMMENT_EDITOR',
OPEN_COMMENT_EDITOR = 'OPEN_COMMENT_EDITOR', OPEN_COMMENT_EDITOR = 'OPEN_COMMENT_EDITOR',
DB_FETCH_COMMENTS = 'DB_FETCH_COMMENTS',
} }

View File

@@ -17,6 +17,7 @@ export enum GlobalActionType {
TEMP = 'TEMP', TEMP = 'TEMP',
CLEAR_ALL_GLOBAL = 'CLEAR_ALL_GLOBAL', CLEAR_ALL_GLOBAL = 'CLEAR_ALL_GLOBAL',
OPEN_POST_WRITE = 'OPEN_POST_WRITE', OPEN_POST_WRITE = 'OPEN_POST_WRITE',
CLOSE_POST_WRITE = 'CLOSE_POST_WRITE' CLOSE_POST_WRITE = 'CLOSE_POST_WRITE',
INIT_LOCALE = 'INIT_LOCALE'
} }

View File

@@ -12,6 +12,8 @@
ADD_VIDEO_POST = 'ADD_VIDEO_POST', ADD_VIDEO_POST = 'ADD_VIDEO_POST',
ADD_POST = 'ADD_POST', ADD_POST = 'ADD_POST',
UPDATE_POST = 'UPDATE_POST', UPDATE_POST = 'UPDATE_POST',
UPDATE_POST_COMMENTS = 'UPDATE_POST_COMMENTS',
UPDATE_POST_VOTES = 'UPDATE_POST_VOTES',
DELETE_POST = 'DELETE_POST', DELETE_POST = 'DELETE_POST',
ADD_LIST_POST = 'ADD_LIST_POST', ADD_LIST_POST = 'ADD_LIST_POST',
CLEAR_ALL_DATA_POST = 'CLEAR_ALL_DATA_POST' CLEAR_ALL_DATA_POST = 'CLEAR_ALL_DATA_POST'

View File

@@ -3,17 +3,38 @@ import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { NavLink, withRouter } from 'react-router-dom' import { NavLink, withRouter } from 'react-router-dom'
import { push } from 'react-router-redux' import { push } from 'react-router-redux'
import Paper from 'material-ui/Paper' import Paper from '@material-ui/core/Paper'
import TextField from 'material-ui/TextField' import TextField from '@material-ui/core/TextField'
import RaisedButton from 'material-ui/Button' import RaisedButton from '@material-ui/core/Button'
import Button from 'material-ui/Button' import Button from '@material-ui/core/Button'
import config from 'src/config' import config from 'src/config'
import { withStyles } from '@material-ui/core/styles'
import { getTranslate, getActiveLanguage } from 'react-localize-redux' import { getTranslate, getActiveLanguage } from 'react-localize-redux'
// - Import actions // - Import actions
import * as authorizeActions from 'actions/authorizeActions' import * as authorizeActions from 'src/store/actions/authorizeActions'
import { IEmailVerificationComponentProps } from './IEmailVerificationComponentProps' import { IEmailVerificationComponentProps } from './IEmailVerificationComponentProps'
import { IEmailVerificationComponentState } from './IEmailVerificationComponentState' import { IEmailVerificationComponentState } from './IEmailVerificationComponentState'
import { Grid } from '@material-ui/core'
const styles = (theme: any) => ({
textField: {
minWidth: 280,
marginTop: 20
},
contain: {
margin: '0 auto'
},
paper: {
minHeight: 370,
maxWidth: 450,
minWidth: 337,
textAlign: 'center',
display: 'block',
margin: 'auto'
}
})
/** /**
* Create component class * Create component class
@@ -22,7 +43,7 @@ import { IEmailVerificationComponentState } from './IEmailVerificationComponentS
* @class EmailVerificationComponent * @class EmailVerificationComponent
* @extends {Component} * @extends {Component}
*/ */
export class EmailVerificationComponent extends Component<IEmailVerificationComponentProps,IEmailVerificationComponentState> { export class EmailVerificationComponent extends Component<IEmailVerificationComponentProps, IEmailVerificationComponentState> {
styles = { styles = {
message: { message: {
@@ -32,7 +53,18 @@ export class EmailVerificationComponent extends Component<IEmailVerificationComp
marginTop: 60 marginTop: 60
}, },
homeButton: { homeButton: {
marginRight: 10 marginRight: 10
},
contain: {
margin: '0 auto'
},
paper: {
minHeight: 370,
maxWidth: 450,
minWidth: 337,
textAlign: 'center',
display: 'block',
margin: 'auto'
} }
} }
@@ -41,7 +73,7 @@ export class EmailVerificationComponent extends Component<IEmailVerificationComp
* Component constructor * Component constructor
* @param {object} props is an object properties of component * @param {object} props is an object properties of component
*/ */
constructor (props: IEmailVerificationComponentProps) { constructor(props: IEmailVerificationComponentProps) {
super(props) super(props)
// Binding function to `this` // Binding function to `this`
@@ -52,47 +84,26 @@ export class EmailVerificationComponent extends Component<IEmailVerificationComp
* Reneder component DOM * Reneder component DOM
* @return {react element} return the DOM which rendered by component * @return {react element} return the DOM which rendered by component
*/ */
render () { render() {
const {translate} = this.props const { translate, classes } = this.props
const paperStyle = {
minHeight: 370,
width: 450,
textAlign: 'center',
display: 'block',
margin: 'auto'
}
return ( return (
<div> <Grid container spacing={24}>
<Grid item xs={12} className={classes.contain}>
<h1 style={{ <h1 className='g__app-name'>{config.settings.appName}</h1>
textAlign: 'center',
padding: '20px',
fontSize: '30px',
fontWeight: 500,
lineHeight: '32px',
margin: 'auto',
color: 'rgba(138, 148, 138, 0.2)'
}}>{config.settings.appName}</h1>
<div className='animate-bottom'> <div className='animate-bottom'>
<Paper style={paperStyle} elevation={1} > <Paper className={classes.paper} elevation={1} >
<div style={{ padding: '48px 40px 36px' }}> <div style={{ padding: '48px 40px 36px' }}>
<div style={{ <div style={{
paddingLeft: '40px', paddingLeft: '40px',
paddingRight: '40px' paddingRight: '40px'
}}> }}>
<h2 style={{ <h2 className='zoomOutLCorner animated g__paper-title'>{translate!('emailVerification.title')}</h2>
textAlign: 'left', </div>
paddingTop: '16px', <p style={this.styles.message as any}>
fontSize: '24px', {translate!('emailVerification.description')}
fontWeight: 400,
lineHeight: '32px',
margin: 0
}} className='zoomOutLCorner animated'>{translate!('emailVerification.title')}</h2>
</div>
<p style={this.styles.message as any}>
{translate!('emailVerification.description')}
</p> </p>
<div style={this.styles.buttons}> <div style={this.styles.buttons}>
<Button variant='raised' style={this.styles.homeButton} color='primary' onClick={() => this.props.homePage()}> {translate!('emailVerification.homeButton')} </Button> <Button variant='raised' style={this.styles.homeButton} color='primary' onClick={() => this.props.homePage()}> {translate!('emailVerification.homeButton')} </Button>
@@ -101,10 +112,11 @@ export class EmailVerificationComponent extends Component<IEmailVerificationComp
<div> <div>
</div> </div>
</div> </div>
</Paper> </Paper>
</div> </div>
</div> </Grid>
</Grid>
) )
} }
} }
@@ -132,9 +144,9 @@ const mapDispatchToProps = (dispatch: Function, ownProps: IEmailVerificationComp
*/ */
const mapStateToProps = (state: any, ownProps: IEmailVerificationComponentProps) => { const mapStateToProps = (state: any, ownProps: IEmailVerificationComponentProps) => {
return { return {
translate: getTranslate(state.locale) translate: getTranslate(state.get('locale'))
} }
} }
// - Connect component to redux store // - Connect component to redux store
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(EmailVerificationComponent as any) as any) export default withRouter<any>(connect(mapStateToProps, mapDispatchToProps)(withStyles(styles as any)(EmailVerificationComponent as any) as any)) as typeof EmailVerificationComponent

View File

@@ -10,6 +10,11 @@ export interface IEmailVerificationComponentProps {
*/ */
homePage: () => any homePage: () => any
/**
* Styles
*/
classes?: any
/** /**
* Translate to locale string * Translate to locale string
*/ */

View File

@@ -0,0 +1,371 @@
// - Import react components
import { HomeRouter } from 'routes'
import { Map } from 'immutable'
import React, { Component } from 'react'
import _ from 'lodash'
import { Route, Switch, withRouter, Redirect, NavLink } from 'react-router-dom'
import { connect } from 'react-redux'
import { push } from 'react-router-redux'
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
import config from 'src/config'
import classNames from 'classnames'
import { withStyles } from '@material-ui/core/styles'
import Drawer from '@material-ui/core/Drawer'
import Menu from '@material-ui/core/Menu'
import MenuList from '@material-ui/core/MenuList'
import MenuItem from '@material-ui/core/MenuItem'
import ListItem from '@material-ui/core/ListItem'
import ListIcon from '@material-ui/core/ListItemIcon'
import ListItemIcon from '@material-ui/core/ListItemIcon'
import ListItemText from '@material-ui/core/ListItemText'
import Divider from '@material-ui/core/Divider'
import SvgArrowLeft from '@material-ui/icons/KeyboardArrowLeft'
import SvgHome from '@material-ui/icons/Home'
import SvgFeedback from '@material-ui/icons/Feedback'
import SvgSettings from '@material-ui/icons/Settings'
import SvgAccountCircle from '@material-ui/icons/AccountCircle'
import SvgPeople from '@material-ui/icons/People'
import List from '@material-ui/core/List'
import Typography from '@material-ui/core/Typography'
import IconButton from '@material-ui/core/IconButton'
import Hidden from '@material-ui/core/Hidden'
import MenuIcon from '@material-ui/icons/Menu'
// - Import app components
import Sidebar from 'src/components/sidebar'
import StreamComponent from 'containers/stream'
import HomeHeader from 'src/components/homeHeader'
import SidebarContent from 'src/components/sidebarContent'
import SidebarMain from 'src/components/sidebarMain'
import Profile from 'containers/profile'
import PostPage from 'containers/postPage'
import People from 'containers/people'
// - Import API
// - Import actions
// - Import actions
import {
authorizeActions,
imageGalleryActions,
postActions,
commentActions,
voteActions,
userActions,
globalActions,
circleActions,
notifyActions
} from 'src/store/actions'
import { IHomeComponentProps } from './IHomeComponentProps'
import { IHomeComponentState } from './IHomeComponentState'
const drawerWidth = 220
const styles = (theme: any) => ({
root: {
width: '100%',
marginTop: theme.spacing.unit * 3,
zIndex: 1,
overflow: 'hidden',
},
appFrame: {
position: 'relative',
display: 'flex',
width: '100%',
height: '100%',
},
navIconHide: {
[theme.breakpoints.up('md')]: {
display: 'none',
},
},
drawerHeader: theme.mixins.toolbar,
drawerPaper: {
maxWidth: drawerWidth,
width: drawerWidth,
[theme.breakpoints.up('md')]: {
width: drawerWidth,
position: 'relative',
height: '100%',
},
},
drawerPaperLarge: {
width: drawerWidth,
[theme.breakpoints.up('md')]: {
width: drawerWidth,
height: '100%',
},
top: 70,
backgroundColor: '#fafafa',
borderRight: 0
},
menu: {
height: '100%',
},
content: {
backgroundColor: 'transparent',
width: '100%',
flexGrow: 1,
padding: theme.spacing.unit * 1,
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen,
}),
height: 'calc(100% - 56px)',
marginTop: 56,
[theme.breakpoints.up('sm')]: {
height: 'calc(100% - 64px)',
marginTop: 64,
},
},
'content-left': {
marginLeft: 0,
},
'content-right': {
marginRight: 0,
},
contentShift: {
transition: theme.transitions.create('margin', {
easing: theme.transitions.easing.easeOut,
duration: theme.transitions.duration.enteringScreen,
}),
},
'contentShift-left': {
marginLeft: 0,
[theme.breakpoints.up('md')]: {
marginLeft: drawerWidth
}
},
'contentShift-right': {
marginRight: 0,
[theme.breakpoints.up('md')]: {
marginRight: drawerWidth
}
}
})
// - Create Home component class
export class HomeComponent extends Component<IHomeComponentProps, IHomeComponentState> {
// Constructor
constructor(props: IHomeComponentProps) {
super(props)
// Default state
this.state = {
drawerOpen: false
}
// Binding function to `this`
}
/**
* Handle drawer toggle
*/
handleDrawerToggle = () => {
this.setState({ drawerOpen: !this.state.drawerOpen })
}
componentWillMount() {
const { global, clearData, loadData, authed, defaultDataEnable, isVerifide, goTo } = this.props
if (!authed) {
goTo!('/login')
return
}
if (!isVerifide) {
goTo!('/emailVerification')
} else if (!global.get('defaultLoadDataStatus')) {
clearData!()
loadData!()
defaultDataEnable!()
}
}
/**
* Render DOM component
*
* @returns DOM
*
* @memberof Home
*/
render() {
const HR = HomeRouter
const { loaded, authed, loadDataStream, mergedPosts, hasMorePosts, showSendFeedback, translate, classes, theme } = this.props
const { drawerOpen } = this.state
const drawer = (
<>
<NavLink to='/'>
<MenuItem style={{ color: 'rgb(117, 117, 117)' }}>
<ListItemIcon>
<SvgHome />
</ListItemIcon>
<ListItemText inset primary={translate!('sidebar.home')} />
</MenuItem>
</NavLink>
<NavLink to={`/${this.props.uid}`}>
<MenuItem style={{ color: 'rgb(117, 117, 117)' }}>
<ListItemIcon>
<SvgAccountCircle />
</ListItemIcon>
<ListItemText inset primary={translate!('sidebar.profile')} />
</MenuItem>
</NavLink>
<NavLink to='/people'>
<MenuItem style={{ color: 'rgb(117, 117, 117)' }}>
<ListItemIcon>
<SvgPeople />
</ListItemIcon>
<ListItemText inset primary={translate!('sidebar.people')} />
</MenuItem>
</NavLink>
<Divider />
<NavLink to='/settings'>
<MenuItem style={{ color: 'rgb(117, 117, 117)' }}>
<ListItemIcon>
<SvgSettings />
</ListItemIcon>
<ListItemText inset primary={translate!('sidebar.settings')} />
</MenuItem>
</NavLink>
<MenuItem onClick={() => showSendFeedback!()} style={{ color: 'rgb(117, 117, 117)' }}>
<ListItemIcon>
<SvgFeedback />
</ListItemIcon>
<ListItemText inset primary={translate!('sidebar.sendFeedback')} />
</MenuItem>
</>
)
const anchor = theme.direction === 'rtl' ? 'right' : 'left'
return (
<div className={classes.root}>
<div className={classes.appFrame}>
<HomeHeader onToggleDrawer={this.handleDrawerToggle} drawerStatus={this.state.drawerOpen} />
<Hidden mdUp>
<Drawer
variant='temporary'
open={this.state.drawerOpen}
classes={{
paper: classes.drawerPaper,
}}
onClose={this.handleDrawerToggle}
ModalProps={{
keepMounted: true, // Better open performance on mobile.
}}
>
<div>
<div className={classes.drawerHeader} />
<MenuList style={{ color: 'rgb(117, 117, 117)', width: '210px' }}>
<Divider />
{drawer}
</MenuList>
</div>
</Drawer>
</Hidden>
<Hidden smDown implementation='js'>
<Drawer
variant='persistent'
open={this.state.drawerOpen}
classes={{
paper: classes.drawerPaperLarge,
}}
>
<div>
<MenuList className={classes.menu} style={{ color: 'rgb(117, 117, 117)', width: '210px' }}>
{drawer}
</MenuList>
</div>
</Drawer>
</Hidden>
<main
className={classNames(classes.content, classes[`content-${anchor}`], {
[classes.contentShift]: drawerOpen,
[classes[`contentShift-${anchor}`]]: drawerOpen,
})}
>
<HR enabled={loaded!} data={{ mergedPosts, loadDataStream, hasMorePosts }} />
</main>
</div>
</div>
)
}
}
// - Map dispatch to props
const mapDispatchToProps = (dispatch: any, ownProps: IHomeComponentProps) => {
return {
loadDataStream:
(page: number, limit: number) => dispatch(postActions.dbGetPosts(page, limit)),
loadData: () => {
dispatch(postActions.dbGetPosts())
dispatch(imageGalleryActions.dbGetImageGallery())
dispatch(userActions.dbGetUserInfo())
dispatch(notifyActions.dbGetNotifications())
dispatch(circleActions.dbGetCircles())
dispatch(circleActions.dbGetUserTies())
dispatch(circleActions.dbGetFollowers())
},
clearData: () => {
dispatch(imageGalleryActions.clearAllData())
dispatch(postActions.clearAllData())
dispatch(userActions.clearAllData())
dispatch(notifyActions.clearAllNotifications())
dispatch(circleActions.clearAllCircles())
dispatch(globalActions.clearTemp())
},
defaultDataDisable: () => {
dispatch(globalActions.defaultDataDisable())
},
defaultDataEnable: () => {
dispatch(globalActions.defaultDataEnable())
},
goTo: (url: string) => dispatch(push(url)),
showSendFeedback: () => dispatch(globalActions.showSendFeedback()),
hideSendFeedback: () => dispatch(globalActions.hideSendFeedback())
}
}
/**
* 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: Map<string, any>, ownProps: IHomeComponentProps) => {
const uid = state.getIn(['authorize', 'uid'], {})
const global = state.get('global', {})
let mergedPosts = Map({})
const circles = state.getIn(['circle', 'circleList'], {})
const followingUsers: Map<string, any> = state.getIn(['circle', 'userTies'], {})
const posts = state.getIn(['post', 'userPosts', uid ], {})
const hasMorePosts = state.getIn(['post', 'stream', 'hasMoreData' ], true)
followingUsers.forEach((user, userId) => {
let newPosts = state.getIn(['post', 'userPosts', userId], {})
mergedPosts = mergedPosts.merge(newPosts)
})
mergedPosts = mergedPosts.merge(posts)
return {
authed: state.getIn(['authorize', 'authed'], false),
isVerifide: state.getIn(['authorize', 'isVerifide'], false),
translate: getTranslate(state.get('locale')),
currentLanguage: getActiveLanguage(state.get('locale')).code,
mergedPosts,
global,
hasMorePosts,
loaded: state.getIn(['user', 'loaded']) && state.getIn(['imageGallery', 'loaded']) && state.getIn(['notify', 'loaded']) && state.getIn(['circle', 'loaded'])
}
}
// - Connect component to redux store
export default withRouter<any>(connect(mapStateToProps, mapDispatchToProps)(withStyles(styles as any, { withTheme: true })(HomeComponent as any) as any)) as typeof HomeComponent

View File

@@ -1,4 +1,4 @@
import { Post } from 'core/domain/posts' import { Post } from 'src/core/domain/posts'
export interface IHomeComponentProps { export interface IHomeComponentProps {
@@ -113,4 +113,14 @@ export interface IHomeComponentProps {
*/ */
translate?: (state: any) => any translate?: (state: any) => any
/**
* Styles
*/
classes?: any
/**
* Theme
*/
theme?: any
} }

View File

@@ -0,0 +1,8 @@
export interface IHomeComponentState {
/**
* Whether drawer is open
*/
drawerOpen: boolean
}

View File

@@ -1,4 +1,4 @@
import { OAuthType } from 'core/domain/authorize' import { OAuthType } from 'src/core/domain/authorize'
export interface ILoginComponentProps { export interface ILoginComponentProps {
/** /**

View File

@@ -0,0 +1,258 @@
// - Import external components
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { NavLink, withRouter } from 'react-router-dom'
import { push } from 'react-router-redux'
import Paper from '@material-ui/core/Paper'
import TextField from '@material-ui/core/TextField'
import RaisedButton from '@material-ui/core/Button'
import Button from '@material-ui/core/Button'
import IconButton from '@material-ui/core/IconButton'
import Divider from '@material-ui/core/Divider'
import ActionAndroid from '@material-ui/icons/Android'
import { withStyles } from '@material-ui/core/styles'
import config from 'src/config'
import { localize } from 'react-localize-redux'
// - Import actions
import * as authorizeActions from 'src/store/actions/authorizeActions'
import { ILoginComponentProps } from './ILoginComponentProps'
import { ILoginComponentState } from './ILoginComponentState'
import { OAuthType } from 'src/core/domain/authorize'
import Grid from '@material-ui/core/Grid/Grid'
import CommonAPI from 'api/CommonAPI'
const styles = (theme: any) => ({
textField: {
minWidth: 280,
marginTop: 20
},
contain: {
margin: '0 auto'
},
paper: {
minHeight: 370,
maxWidth: 450,
minWidth: 337,
textAlign: 'center',
display: 'block',
margin: 'auto'
},
bottomPaper: {
display: 'inherit',
fontSize: 'small',
marginTop: '50px'
},
link: {
color: '#0095ff',
display: 'inline-block'
}
})
// - Create Login component class
export class LoginComponent extends Component<ILoginComponentProps, ILoginComponentState> {
styles = {
singinOptions: {
paddingBottom: 10,
justifyContent: 'space-around',
display: 'flex'
},
divider: {
marginBottom: 10,
marginTop: 15
}
}
/**
* Component constructor
* @param {object} props is an object properties of component
*/
constructor(props: ILoginComponentProps) {
super(props)
this.state = {
emailInput: '',
emailInputError: '',
passwordInput: '',
passwordInputError: '',
confirmInputError: ''
}
// Binding function to `this`
this.handleForm = this.handleForm.bind(this)
}
/**
* Handle data on input change
* @param {event} evt is an event of inputs of element on change
*/
handleInputChange = (event: any) => {
const target = event.target
const value = target.type === 'checkbox' ? target.checked : target.value
const name = target.name
this.setState({
[name]: value
})
switch (name) {
case 'emailInput':
this.setState({
emailInputError: ''
})
break
case 'passwordInput':
this.setState({
confirmInputError: '',
passwordInputError: ''
})
break
default:
}
}
/**
* Handle register form
*/
handleForm = () => {
const { translate } = this.props
let error = false
if (this.state.emailInput === '') {
this.setState({
emailInputError: translate!('login.emailRequiredError')
})
error = true
}
if (this.state.passwordInput === '') {
this.setState({
passwordInputError: translate!('login.passwordRequiredError')
})
error = true
}
if (!error) {
this.props.login!(
this.state.emailInput,
this.state.passwordInput
)
}
}
/**
* Reneder component DOM
* @return {react element} return the DOM which rendered by component
*/
render() {
const { classes, loginWithOAuth, translate } = this.props
const OAuthLogin = (
<div style={this.styles.singinOptions as any}>
<IconButton
onClick={() => loginWithOAuth!(OAuthType.FACEBOOK)}
><div className='icon-fb icon'></div></IconButton>
<IconButton
onClick={() => loginWithOAuth!(OAuthType.GOOGLE)}
> <div className='icon-google icon'></div> </IconButton>
<IconButton
onClick={() => loginWithOAuth!(OAuthType.GITHUB)}
> <div className='icon-github icon'></div> </IconButton>
</div>
)
return (
<Grid container spacing={24}>
<Grid item xs={12} className={classes.contain}>
<h1 className='g__app-name'>{config.settings.appName}</h1>
<div className='animate-bottom'>
<Paper className={classes.paper} elevation={1} >
<form>
<div style={{ padding: '48px 40px 36px' }}>
<div style={{
paddingLeft: '40px',
paddingRight: '40px'
}}>
<h2 className='zoomOutLCorner animated g__paper-title'>{translate!('login.title')}</h2>
</div>
{config.settings.enabledOAuthLogin ? OAuthLogin : ''}
<Divider style={this.styles.divider} />
<TextField
className={classes.textField}
autoFocus
onChange={this.handleInputChange}
helperText={this.state.emailInputError}
error={this.state.emailInputError.trim() !== ''}
name='emailInput'
label={translate!('login.emailLabel')}
type='email'
tabIndex={1}
/><br />
<TextField
className={classes.textField}
onChange={this.handleInputChange}
helperText={this.state.passwordInputError}
error={this.state.passwordInputError.trim() !== ''}
name='passwordInput'
label={translate!('login.passwordLabel')}
type='password'
tabIndex={2}
/><br />
<br />
<br />
<div className='login__button-box'>
<div>
<Button onClick={this.props.signupPage} tabIndex={4}>{translate!('login.createAccountButton')}</Button>
</div>
<div >
<Button variant='raised' color='primary' onClick={this.handleForm} tabIndex={3} >{translate!('login.loginButton')}</Button>
</div>
</div>
<span className={classes.bottomPaper}>{translate!('login.forgetPasswordMessage')} <NavLink to='/resetPassword' className={classes.link}>{translate!('login.resetPasswordLabel')}</NavLink></span>
</div>
</form>
</Paper>
</div>
</Grid>
</Grid>
)
}
}
/**
* Map dispatch to props
* @param {func} dispatch is the function to dispatch action to reducers
* @param {object} ownProps is the props belong to component
* @return {object} props of component
*/
const mapDispatchToProps = (dispatch: any, ownProps: ILoginComponentProps) => {
return {
login: (email: string, password: string) => {
dispatch(authorizeActions.dbLogin(email, password))
},
loginWithOAuth: (type: OAuthType) => dispatch(authorizeActions.dbLoginWithOAuth(type)),
signupPage: () => {
dispatch(push('/signup'))
}
}
}
/**
* Map state to props
*/
const mapStateToProps = (state: any, ownProps: ILoginComponentProps) => {
return {
}
}
// - Connect component to redux store
export default withRouter<any>(connect(mapStateToProps, mapDispatchToProps)(withStyles(styles as any)(localize(LoginComponent, 'locale', CommonAPI.getStateSlice) as any) as any)) as typeof LoginComponent

View File

@@ -1,4 +1,4 @@
import { User } from 'core/domain/users' import { User } from 'src/core/domain/users'
export interface IMasterComponentProps { export interface IMasterComponentProps {
/** /**
* Close gloal message * Close gloal message

View File

@@ -4,18 +4,20 @@ import React, { Component } from 'react'
import { connect } from 'react-redux' import { connect } from 'react-redux'
import { Route, Switch, NavLink, withRouter, Redirect } from 'react-router-dom' import { Route, Switch, NavLink, withRouter, Redirect } from 'react-router-dom'
import { push } from 'react-router-redux' import { push } from 'react-router-redux'
import Snackbar from 'material-ui/Snackbar' import Snackbar from '@material-ui/core/Snackbar'
import { LinearProgress } from 'material-ui/Progress' import LinearProgress from '@material-ui/core/LinearProgress'
import {Helmet} from 'react-helmet'
import {Map} from 'immutable'
// - Import components // - Import components
import MasterLoading from 'components/masterLoading' import MasterLoading from 'src/components/masterLoading'
import SendFeedback from 'components/sendFeedback' import SendFeedback from 'src/components/sendFeedback'
import MasterRouter from 'routes/MasterRouter' import MasterRouter from 'src/routes/MasterRouter'
import { IMasterComponentProps } from './IMasterComponentProps' import { IMasterComponentProps } from './IMasterComponentProps'
import { IMasterComponentState } from './IMasterComponentState' import { IMasterComponentState } from './IMasterComponentState'
import { ServiceProvide, IServiceProvider } from 'core/factories' import { ServiceProvide, IServiceProvider } from 'src/core/factories'
import { IAuthorizeService } from 'core/services/authorize' import { IAuthorizeService } from 'src/core/services/authorize'
// - Import actions // - Import actions
import { import {
@@ -28,7 +30,7 @@ import {
globalActions, globalActions,
circleActions, circleActions,
notifyActions notifyActions
} from 'actions' } from 'src/store/actions'
/* ------------------------------------ */ /* ------------------------------------ */
@@ -53,7 +55,6 @@ export class MasterComponent extends Component<IMasterComponentProps, IMasterCom
} }
// Binding functions to `this` // Binding functions to `this`
this.handleLoading = this.handleLoading.bind(this)
this.handleMessage = this.handleMessage.bind(this) this.handleMessage = this.handleMessage.bind(this)
} }
@@ -63,14 +64,6 @@ export class MasterComponent extends Component<IMasterComponentProps, IMasterCom
this.props.closeMessage() this.props.closeMessage()
} }
// Handle loading
handleLoading = (status: boolean) => {
this.setState({
loading: status,
authed: false
})
}
componentDidCatch (error: any, info: any) { componentDidCatch (error: any, info: any) {
console.log('===========Catched by React componentDidCatch==============') console.log('===========Catched by React componentDidCatch==============')
console.log(error, info) console.log(error, info)
@@ -130,6 +123,11 @@ export class MasterComponent extends Component<IMasterComponentProps, IMasterCom
return ( return (
<div id='master'> <div id='master'>
<Helmet>
<meta charSet='utf-8' />
<title>React Social Network</title>
<link rel='canonical' href='https://github.com/Qolzam/react-social-network' />
</Helmet>
{sendFeedbackStatus ? <SendFeedback /> : ''} {sendFeedbackStatus ? <SendFeedback /> : ''}
<div className='master__progress' style={{ display: (progress.visible ? 'block' : 'none') }}> <div className='master__progress' style={{ display: (progress.visible ? 'block' : 'none') }}>
<LinearProgress variant='determinate' value={progress.percent} /> <LinearProgress variant='determinate' value={progress.percent} />
@@ -137,7 +135,7 @@ export class MasterComponent extends Component<IMasterComponentProps, IMasterCom
<div className='master__loading animate-fading2' style={{ display: (global.showTopLoading ? 'flex' : 'none') }}> <div className='master__loading animate-fading2' style={{ display: (global.showTopLoading ? 'flex' : 'none') }}>
<div className='title'>Loading ... </div> <div className='title'>Loading ... </div>
</div> </div>
<MasterLoading activeLoading={global.showMasterLoading} handleLoading={this.handleLoading} /> {progress.visible ? <MasterLoading /> : ''}
<MasterRouter enabled={!loading} data={{uid}} /> <MasterRouter enabled={!loading} data={{uid}} />
<Snackbar <Snackbar
open={this.props.global.messageOpen} open={this.props.global.messageOpen}
@@ -196,15 +194,16 @@ const mapDispatchToProps = (dispatch: any, ownProps: IMasterComponentProps) => {
* Map state to props * Map state to props
* @param {object} state * @param {object} state
*/ */
const mapStateToProps = (state: any) => { const mapStateToProps = (state: Map<string, any>) => {
const { authorize, global, user, post, comment, imageGallery, vote, notify, circle } = state const authorize = Map(state.get('authorize', {})).toJS()
const { sendFeedbackStatus } = global const global = Map(state.get('global', {})).toJS()
const { sendFeedbackStatus, progress } = global
return { return {
sendFeedbackStatus, sendFeedbackStatus,
progress,
guest: authorize.guest, guest: authorize.guest,
uid: authorize.uid, uid: authorize.uid,
authed: authorize.authed, authed: authorize.authed,
progress: global.progress,
global: global global: global
} }

Some files were not shown because too many files have changed in this diff Show More