20
CHANGELOG.md
@@ -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)
|
||||||
|
|||||||
33
README.md
@@ -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**
|
||||||
|
|
||||||
[](https://www.youtube.com/watch?v=zrqDE82Eny8)
|
[](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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
After Width: | Height: | Size: 195 KiB |
BIN
docs/app/layer-2.png
Normal file
|
After Width: | Height: | Size: 241 KiB |
BIN
docs/app/layer-3.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
docs/app/layer-4.png
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
docs/app/layer-5.png
Normal file
|
After Width: | Height: | Size: 177 KiB |
BIN
docs/app/layer-6.png
Normal file
|
After Width: | Height: | Size: 220 KiB |
BIN
docs/app/layer.png
Normal file
|
After Width: | Height: | Size: 289 KiB |
28
docs/layers.md
Normal 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">
|
||||||
@@ -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.
|
|
||||||
@@ -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).
|
|
||||||
@@ -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>
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -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
67
package.json
@@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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')
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'])
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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'])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 )
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 || ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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'])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
41
src/components/shareDialog/IShareDialogComponentProps.ts
Normal 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
|
||||||
|
|
||||||
|
}
|
||||||
4
src/components/shareDialog/IShareDialogComponentState.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
export interface IShareDialogComponentState {
|
||||||
|
|
||||||
|
}
|
||||||
213
src/components/shareDialog/ShareDialogComponent.tsx
Normal 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)
|
||||||
2
src/components/shareDialog/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
import ShareDialogComponent from './ShareDialogComponent'
|
||||||
|
export default ShareDialogComponent
|
||||||
@@ -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'
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
@@ -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'])
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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',
|
||||||
}
|
}
|
||||||
@@ -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'
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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'
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -10,6 +10,11 @@ export interface IEmailVerificationComponentProps {
|
|||||||
*/
|
*/
|
||||||
homePage: () => any
|
homePage: () => any
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Styles
|
||||||
|
*/
|
||||||
|
classes?: any
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Translate to locale string
|
* Translate to locale string
|
||||||
*/
|
*/
|
||||||
371
src/containers/home/HomeComponent.tsx
Normal 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
|
||||||
@@ -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
|
||||||
|
|
||||||
}
|
}
|
||||||
8
src/containers/home/IHomeComponentState.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
export interface IHomeComponentState {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether drawer is open
|
||||||
|
*/
|
||||||
|
drawerOpen: boolean
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { OAuthType } from 'core/domain/authorize'
|
import { OAuthType } from 'src/core/domain/authorize'
|
||||||
export interface ILoginComponentProps {
|
export interface ILoginComponentProps {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
258
src/containers/login/LoginComponent.tsx
Normal 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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||