20
CHANGELOG.md
@@ -1,23 +1,37 @@
|
||||
# Change Log
|
||||
|
||||
## [Unreleased](https://github.com/Qolzam/react-social-network/tree/HEAD)
|
||||
|
||||
[Full Changelog](https://github.com/Qolzam/react-social-network/compare/v0.5.0...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)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- 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)
|
||||
- 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)
|
||||
- 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)
|
||||
- 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:**
|
||||
|
||||
- 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)
|
||||
- \[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)
|
||||
- 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)
|
||||
[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>
|
||||
</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/).
|
||||
For those who prefer writing code by typescript, now React Social Network support both javascript and typescript language.
|
||||
|
||||
## 🌟New Upgrade :
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
[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
|
||||
|
||||
Comming soon :) ...
|
||||
Comming soon :) ...
|
||||
|
||||
|
||||
## Roadmap
|
||||
@@ -71,6 +78,8 @@ and then install the package
|
||||
|
||||
### 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. Clone your fork to your local machine
|
||||
```bash
|
||||
@@ -94,7 +103,7 @@ and then install the package
|
||||
**Video tutorial**
|
||||
|
||||
[](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:
|
||||
* 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.
|
||||
@@ -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:
|
||||
* In the Firebase console, open the Auth section.
|
||||
* 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)
|
||||
* 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 ...
|
||||
#### [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 ...
|
||||
1. Go ahead ;)
|
||||
```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.
|
||||
### Features
|
||||
- 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.
|
||||
- [InversifyJS](http://inversify.io/) as IOC container
|
||||
- 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 scroll auto loading for show posts and people pages.
|
||||
- Using [Firestore](https://firebase.google.com/docs/firestore/)
|
||||
- Supportig `Right To Left`
|
||||
- Some cool stuff :)
|
||||
## 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).
|
||||
@@ -172,6 +184,7 @@ There are three main layers:
|
||||
```bash
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
* [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.
|
||||
* [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.
|
||||
* [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.
|
||||
@@ -203,6 +217,9 @@ We use [SemVer](http://semver.org/) for versioning. For the versions available,
|
||||
- Amir Movahedi
|
||||
- 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
|
||||
|
||||
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>
|
||||
</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/).
|
||||
For those who prefer writing code by typescript, now React Social Network support both javascript and typescript language.
|
||||
|
||||
## 🌟New Upgrade :
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
## 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
|
||||
|
||||
[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
|
||||
|
||||
Comming soon :) ...
|
||||
Comming soon :) ...
|
||||
|
||||
|
||||
## 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.
|
||||
### Features
|
||||
- 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.
|
||||
- [InversifyJS](http://inversify.io/) as IOC container
|
||||
- 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 scroll auto loading for show posts and people pages.
|
||||
- Using [Firestore](https://firebase.google.com/docs/firestore/)
|
||||
- Supportig `Right To Left`
|
||||
- Some cool stuff :)
|
||||
## 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).
|
||||
@@ -172,6 +182,7 @@ There are three main layers:
|
||||
```bash
|
||||
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
|
||||
|
||||
@@ -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.
|
||||
* [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.
|
||||
* [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.
|
||||
* [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.
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
## Layers
|
||||
|
||||
* [App](layers/app.md)
|
||||
* [Overview](layers.md)
|
||||
* [Components](layers/components.md)
|
||||
* [API](layers/api.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
|
||||
|
||||
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
|
||||
|
||||
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": {
|
||||
"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": [
|
||||
{
|
||||
"source": "/favicon.ico",
|
||||
|
||||
12767
package-lock.json
generated
67
package.json
@@ -13,26 +13,32 @@
|
||||
"build": "npm-run-all build-css build-js",
|
||||
"test": "react-scripts-ts test --env=jsdom",
|
||||
"eject": "react-scripts-ts eject",
|
||||
"watch": "webpack -w",
|
||||
"deploy:firebase": "npm run build && firebase deploy"
|
||||
},
|
||||
"author": "Amir Movahedi",
|
||||
"license": "MIT",
|
||||
"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",
|
||||
"axios": "^0.16.2",
|
||||
"classnames": "^2.2.5",
|
||||
"copy-to-clipboard": "^3.0.8",
|
||||
"crypto-js": "^3.1.9-1",
|
||||
"faker": "^4.1.0",
|
||||
"firebase": "^4.6.2",
|
||||
"firebase": "^4.11.0",
|
||||
"immutable": "^3.8.2",
|
||||
"install": "^0.10.2",
|
||||
"inversify": "^4.6.0",
|
||||
"jss-rtl": "^0.2.1",
|
||||
"keycode": "^2.1.9",
|
||||
"lodash": "^4.17.4",
|
||||
"material-ui": "^1.0.0-beta.33",
|
||||
"material-ui-icons": "^1.0.0-beta.17",
|
||||
"moment": "^2.18.1",
|
||||
"morgan": "^1.8.1",
|
||||
"node-sass-chokidar": "0.0.3",
|
||||
@@ -42,21 +48,26 @@
|
||||
"react": "^16.2.0",
|
||||
"react-addons-test-utils": "^15.6.2",
|
||||
"react-avatar-editor": "^10.3.0",
|
||||
"react-day-picker": "^7.0.7",
|
||||
"react-dom": "^16.2.0",
|
||||
"react-event-listener": "^0.5.1",
|
||||
"react-helmet": "^5.2.0",
|
||||
"react-infinite-scroller": "^1.1.2",
|
||||
"react-linkify": "^0.2.1",
|
||||
"react-localize-redux": "^2.15.1",
|
||||
"react-parallax": "^1.4.4",
|
||||
"react-loadable": "^5.3.1",
|
||||
"react-localize-redux": "^2.17.2",
|
||||
"react-parallax": "^1.6.1",
|
||||
"react-redux": "^5.0.6",
|
||||
"react-router": "^4.1.1 ",
|
||||
"react-router-dom": "^4.1.1",
|
||||
"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-tap-event-plugin": "^3.0.2",
|
||||
"redux": "^3.7.2",
|
||||
"redux-actions": "^2.0.3",
|
||||
"redux-immutable": "^4.0.0",
|
||||
"redux-saga": "^0.16.0",
|
||||
"redux-thunk": "^2.2.0",
|
||||
"reflect-metadata": "^0.1.10",
|
||||
"save": "^2.3.0",
|
||||
@@ -67,7 +78,7 @@
|
||||
"@types/classnames": "^2.2.3",
|
||||
"@types/jest": "^22.1.1",
|
||||
"@types/lodash": "^4.14.77",
|
||||
"@types/node": "^9.4.0",
|
||||
"@types/node": "^9.6.2",
|
||||
"@types/prop-types": "^15.5.2",
|
||||
"@types/react": "^16.0.36",
|
||||
"@types/react-dom": "^16.0.3",
|
||||
@@ -79,15 +90,39 @@
|
||||
"@types/react-tap-event-plugin": "0.0.30",
|
||||
"@types/redux-logger": "^3.0.4",
|
||||
"@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",
|
||||
"ts-node": "^3.3.0",
|
||||
"tslint": "^5.7.0",
|
||||
"ts-node": "^5.0.1",
|
||||
"tslint": "^5.9.1",
|
||||
"tslint-config-standard": "^6.0.1",
|
||||
"typescript": "^2.7.1"
|
||||
"typescript": "^2.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "8.9.4",
|
||||
"npm": "5.6.0"
|
||||
"directories": {
|
||||
"doc": "docs"
|
||||
},
|
||||
"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 {
|
||||
margin: 0;
|
||||
background-color: #eeeeee;
|
||||
background-color: #fafafa;
|
||||
height: 100%
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@
|
||||
padding: 0.01em 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<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 data log data object
|
||||
*/
|
||||
const logger = (title: string, data: any) => {
|
||||
const logger = (title: string, ...data: any[]) => {
|
||||
const randomColor = getRandomColor()
|
||||
|
||||
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 =========================================`)
|
||||
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`)
|
||||
window['console']['log'](``)
|
||||
window['console']['log'](` `, data)
|
||||
window['console']['log'](`\n =========================================`)
|
||||
|
||||
}
|
||||
|
||||
@@ -24,7 +27,18 @@ const getRandomColor = () => {
|
||||
return color
|
||||
}
|
||||
|
||||
const updateObject = (oldObject: any, updatedProperties: any) => {
|
||||
return {
|
||||
...oldObject,
|
||||
...updatedProperties
|
||||
}
|
||||
}
|
||||
|
||||
const getStateSlice = (state: any) => state.toJS()['locale']
|
||||
|
||||
export default {
|
||||
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
|
||||
const getExtension = (fileName: string) => {
|
||||
let re: RegExp = /(?:\.([^.]+))?$/
|
||||
@@ -36,9 +25,9 @@ const constraintImage = (file: File,fileName: string, maxWidth?: number, maxHeig
|
||||
if (file.type.match(/image.*/)) {
|
||||
// Load the image
|
||||
let reader = new FileReader()
|
||||
reader.onload = function (readerEvent: FileReaderEvent) {
|
||||
reader.onload = (readerEvent: any) => {
|
||||
let image = new Image()
|
||||
image.onload = function (imageEvent: Event) {
|
||||
image.onload = (imageEvent: Event) => {
|
||||
|
||||
// Resize the image
|
||||
let canvas: HTMLCanvasElement = document.createElement('canvas')
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {List} from 'immutable'
|
||||
|
||||
// Get tags from post content
|
||||
export const detectTags: (content: string, character: string) => string[] = (content: string, character: string) => {
|
||||
@@ -27,3 +28,15 @@ export const sortObjectsDate = (objects: any) => {
|
||||
|
||||
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 { connect } from 'react-redux'
|
||||
import { push } from 'react-router-redux'
|
||||
import List, {
|
||||
ListItem,
|
||||
ListItemIcon,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText
|
||||
} from 'material-ui/List'
|
||||
import SvgGroup from 'material-ui-icons/GroupWork'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import MoreVertIcon from 'material-ui-icons/MoreVert'
|
||||
import TextField from 'material-ui/TextField'
|
||||
import { MenuList, MenuItem } from 'material-ui/Menu'
|
||||
import { withStyles } from 'material-ui/styles'
|
||||
import {Map, List as ImuList} from 'immutable'
|
||||
|
||||
// - Material UI
|
||||
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 SvgGroup from '@material-ui/icons/GroupWork'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert'
|
||||
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 Grow from 'material-ui/transitions/Grow'
|
||||
import ClickAwayListener from 'material-ui/utils/ClickAwayListener'
|
||||
import Grow from '@material-ui/core/Grow'
|
||||
import ClickAwayListener from '@material-ui/core/ClickAwayListener'
|
||||
import classNames from 'classnames'
|
||||
import IconButtonElement from 'layouts/IconButtonElement'
|
||||
import Dialog, {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle
|
||||
} from 'material-ui/Dialog'
|
||||
import Divider from 'material-ui/Divider'
|
||||
import Button from 'material-ui/Button'
|
||||
import RaisedButton from 'material-ui/Button'
|
||||
import SvgClose from 'material-ui-icons/Close'
|
||||
import AppBar from 'material-ui/AppBar'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import Collapse from 'material-ui/transitions/Collapse'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||
import Divider from '@material-ui/core/Divider'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import RaisedButton from '@material-ui/core/Button'
|
||||
import SvgClose from '@material-ui/icons/Close'
|
||||
import AppBar from '@material-ui/core/AppBar'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import Collapse from '@material-ui/core/Collapse'
|
||||
|
||||
// - Import app components
|
||||
import UserAvatar from 'components/userAvatar'
|
||||
@@ -39,7 +41,7 @@ import UserAvatar from 'components/userAvatar'
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as circleActions from 'actions/circleActions'
|
||||
import * as circleActions from 'store/actions/circleActions'
|
||||
|
||||
import { ICircleComponentProps } from './ICircleComponentProps'
|
||||
import { ICircleComponentState } from './ICircleComponentState'
|
||||
@@ -61,6 +63,14 @@ const styles = (theme: any) => ({
|
||||
},
|
||||
dialogPaper: {
|
||||
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
|
||||
*/
|
||||
circleName: this.props.circle.name,
|
||||
circleName: this.props.circle.get('name', ''),
|
||||
/**
|
||||
* 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 = []
|
||||
|
||||
if (usersOfCircle) {
|
||||
Object.keys(usersOfCircle).forEach((userId, index) => {
|
||||
const { fullName } = usersOfCircle[userId]
|
||||
let avatar = usersOfCircle && usersOfCircle[userId] ? usersOfCircle[userId].avatar || '' : ''
|
||||
usersOfCircle.forEach((user: Map<string, any>, userId) => {
|
||||
const fullName = user.get('fullName')
|
||||
let avatar = user.get('avatar', '')
|
||||
usersParsed.push(
|
||||
<ListItem
|
||||
button
|
||||
@@ -295,9 +305,9 @@ export class CircleComponent extends Component<ICircleComponentProps, ICircleCom
|
||||
<ListItemIcon className={classes.icon}>
|
||||
<SvgGroup />
|
||||
</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>
|
||||
{circle.isSystem ? null : rightIconMenu}
|
||||
{circle.get('isSystem') ? null : rightIconMenu}
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>
|
||||
<Collapse component='li' in={this.state.open} timeout='auto' unmountOnExit>
|
||||
@@ -306,6 +316,7 @@ export class CircleComponent extends Component<ICircleComponentProps, ICircleCom
|
||||
</List>
|
||||
</Collapse>
|
||||
<Dialog
|
||||
PaperProps={{className: classes.fullPageXs}}
|
||||
key={this.props.id}
|
||||
open={this.props.openSetting!}
|
||||
onClose={this.props.closeCircleSettings}
|
||||
@@ -354,30 +365,27 @@ const mapDispatchToProps = (dispatch: any, ownProps: ICircleComponentProps) => {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: ICircleComponentProps) => {
|
||||
const { circle, authorize, server } = state
|
||||
const { userTies } = circle
|
||||
const { uid } = state.authorize
|
||||
const circles: { [circleId: string]: Circle } = circle ? (circle.circleList || {}) : {}
|
||||
const currentCircle = (circles ? circles[ownProps.id] : {}) as Circle
|
||||
const circleId = ownProps.circle.id!
|
||||
let usersOfCircle: { [userId: string]: UserTie } = {}
|
||||
Object.keys(userTies).forEach((userTieId) => {
|
||||
const theUserTie = userTies[userTieId] as UserTie
|
||||
if (theUserTie.circleIdList!.indexOf(ownProps.id) > -1) {
|
||||
usersOfCircle = {
|
||||
...usersOfCircle,
|
||||
[userTieId]: theUserTie
|
||||
}
|
||||
const mapStateToProps = (state: Map<string, any>, ownProps: ICircleComponentProps) => {
|
||||
const userTies: Map<string, any> = state.getIn(['circle', 'userTies'])
|
||||
const uid = state.getIn(['authorize', 'uid'])
|
||||
const circles: Map<string, Map<string, any>> = state.getIn(['circle', 'circleList'], {})
|
||||
const currentCircle: Map<string, any> = circles.get(ownProps.id, Map({}))
|
||||
const circleId = ownProps.circle.get('id')
|
||||
let usersOfCircle: Map<string, any> = Map({})
|
||||
userTies.forEach((userTie , userTieId) => {
|
||||
const theUserTie: Map<string, any> = userTie
|
||||
const circleList: ImuList<string> = theUserTie.getIn(['circleIdList'])
|
||||
if ( circleList.indexOf(ownProps.id) > -1) {
|
||||
usersOfCircle = usersOfCircle.set(userTieId!, theUserTie)
|
||||
}
|
||||
})
|
||||
return {
|
||||
usersOfCircle,
|
||||
openSetting: (state.circle && state.circle.openSetting && state.circle.openSetting[circleId]) ? state.circle.openSetting[circleId] : false,
|
||||
userInfo: state.user.info
|
||||
openSetting: state.getIn(['circle', 'openSetting', circleId], false),
|
||||
userInfo: state.getIn(['user', 'info'])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - 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 { Profile } from 'core/domain/users'
|
||||
import { Circle, UserTie } from 'core/domain/circles'
|
||||
import {Map} from 'immutable'
|
||||
|
||||
export interface ICircleComponentProps {
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @type {Circle}
|
||||
* @memberof ICircleComponentProps
|
||||
* Circle
|
||||
*/
|
||||
circle: Circle
|
||||
circle: Map<string, any>
|
||||
|
||||
/**
|
||||
* Circle identifier
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
id: string
|
||||
|
||||
/**
|
||||
* User identifier
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
uid: string
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICircleComponentProps
|
||||
* Update circle
|
||||
*/
|
||||
updateCircle?: Function
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICircleComponentProps
|
||||
* Delete circle
|
||||
*/
|
||||
deleteCircle?: Function
|
||||
|
||||
/**
|
||||
* Users of current circle
|
||||
*/
|
||||
usersOfCircle?: {[userId: string]: UserTie}
|
||||
usersOfCircle?: Map<string, any>
|
||||
|
||||
/**
|
||||
* Close setting box of circle
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
closeCircleSettings?: any
|
||||
|
||||
/**
|
||||
* Circle setting dialog is open {true} or not {false}
|
||||
*
|
||||
* @type {Function}
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
openSetting?: boolean
|
||||
|
||||
/**
|
||||
* Change route location
|
||||
*
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
goTo?: (url: string) => void
|
||||
|
||||
/**
|
||||
* Open setting box for a circle
|
||||
*
|
||||
* @memberof ICircleComponentProps
|
||||
*/
|
||||
openCircleSettings?: () => any
|
||||
|
||||
|
||||
@@ -1,29 +1,37 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { findDOMNode } from 'react-dom'
|
||||
import { connect } from 'react-redux'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import PropTypes from 'prop-types'
|
||||
import moment from 'moment/moment'
|
||||
import Linkify from 'react-linkify'
|
||||
import Popover from '@material-ui/core/Popover'
|
||||
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
|
||||
import {Map} from 'immutable'
|
||||
|
||||
import { Comment } from 'core/domain/comments'
|
||||
|
||||
// - Import material UI libraries
|
||||
import Divider from 'material-ui/Divider'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import Button from 'material-ui/Button'
|
||||
import { grey } from 'material-ui/colors'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import MoreVertIcon from 'material-ui-icons/MoreVert'
|
||||
import List, { ListItem, ListItemIcon, ListItemText } from 'material-ui/List'
|
||||
import Menu, { MenuList, MenuItem } from 'material-ui/Menu'
|
||||
import TextField from 'material-ui/TextField'
|
||||
import { withStyles } from 'material-ui/styles'
|
||||
import Divider from '@material-ui/core/Divider'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import { grey } from '@material-ui/core/colors'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert'
|
||||
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 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 { Card, CardActions, CardHeader, CardMedia, CardContent } from 'material-ui'
|
||||
import Grow from 'material-ui/transitions/Grow'
|
||||
import ClickAwayListener from 'material-ui/utils/ClickAwayListener'
|
||||
import { Card, CardActions, CardHeader, CardMedia, CardContent } from '@material-ui/core'
|
||||
import Grow from '@material-ui/core/Grow'
|
||||
import ClickAwayListener from '@material-ui/core/ClickAwayListener'
|
||||
import classNames from 'classnames'
|
||||
|
||||
// - Import app components
|
||||
@@ -32,8 +40,8 @@ import UserAvatar from 'components/userAvatar'
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as commentActions from 'actions/commentActions'
|
||||
import * as userActions from 'actions/userActions'
|
||||
import * as commentActions from 'store/actions/commentActions'
|
||||
import * as userActions from 'store/actions/userActions'
|
||||
|
||||
import { ICommentComponentProps } from './ICommentComponentProps'
|
||||
import { ICommentComponentState } from './ICommentComponentState'
|
||||
@@ -90,7 +98,6 @@ const styles = (theme: any) => ({
|
||||
* Create component class
|
||||
*/
|
||||
export class CommentComponent extends Component<ICommentComponentProps, ICommentComponentState> {
|
||||
|
||||
static propTypes = {
|
||||
/**
|
||||
* Comment object
|
||||
@@ -106,6 +113,11 @@ export class CommentComponent extends Component<ICommentComponentProps, IComment
|
||||
disableComments: PropTypes.bool
|
||||
}
|
||||
|
||||
/**
|
||||
* Fields
|
||||
*/
|
||||
buttonMenu = null
|
||||
|
||||
/**
|
||||
* DOM styles
|
||||
*
|
||||
@@ -181,7 +193,11 @@ export class CommentComponent extends Component<ICommentComponentProps, IComment
|
||||
/**
|
||||
* 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) => {
|
||||
const { comment } = this.props
|
||||
comment.editorStatus = undefined
|
||||
comment.text = this.state.text
|
||||
this.props.update!(comment)
|
||||
this.setState({
|
||||
@@ -265,7 +280,7 @@ export class CommentComponent extends Component<ICommentComponentProps, IComment
|
||||
* Handle comment menu
|
||||
*/
|
||||
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 () {
|
||||
const { userId } = this.props.comment
|
||||
if (!this.props.isCommentOwner && !this.props.info![userId!]) {
|
||||
const { commentOwner } = this.props
|
||||
if (!this.props.isCommentOwner && !commentOwner) {
|
||||
this.props.getUserInfo!()
|
||||
}
|
||||
}
|
||||
@@ -291,30 +306,44 @@ export class CommentComponent extends Component<ICommentComponentProps, IComment
|
||||
/**
|
||||
* 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 = () => (
|
||||
<Manager
|
||||
className={classes.iconButton}
|
||||
>
|
||||
<Target>
|
||||
const rightIconMenu = (
|
||||
<>
|
||||
<IconButton
|
||||
buttonRef={(node: any) => {
|
||||
this.buttonMenu = node
|
||||
}}
|
||||
aria-owns={openMenu! ? 'comment-menu' : ''}
|
||||
aria-haspopup='true'
|
||||
onClick={this.handleCommentMenu}
|
||||
>
|
||||
<MoreVertIcon className={classes.moreIcon} />
|
||||
</IconButton>
|
||||
</Target>
|
||||
<Popper
|
||||
{/* <Popper
|
||||
placement='bottom-start'
|
||||
eventsEnabled={openMenu!}
|
||||
className={classNames({ [classes.popperClose]: !openMenu! }, { [classes.popperOpen]: openMenu! })}
|
||||
>
|
||||
<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>
|
||||
<MenuList role='menu'>
|
||||
<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>) : ''}
|
||||
</MenuList>
|
||||
</Paper>
|
||||
</Grow>
|
||||
</ClickAwayListener>
|
||||
</Popper>
|
||||
</Manager>
|
||||
</Popover>
|
||||
|
||||
</>
|
||||
)
|
||||
|
||||
const Author = () => (
|
||||
@@ -336,7 +364,7 @@ export class CommentComponent extends Component<ICommentComponentProps, IComment
|
||||
}}>{moment.unix(comment.creationDate!).fromNow()}</span>
|
||||
</div>
|
||||
)
|
||||
const { userId, editorStatus } = comment
|
||||
const { userId } = comment
|
||||
const commentBody = (
|
||||
<div style={{ outline: 'none', flex: 'auto', flexGrow: 1 }}>
|
||||
{ editorStatus ? <TextField
|
||||
@@ -373,7 +401,7 @@ export class CommentComponent extends Component<ICommentComponentProps, IComment
|
||||
title={editorStatus ? '' : <Author />}
|
||||
subheader={commentBody}
|
||||
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>
|
||||
|
||||
@@ -410,15 +438,16 @@ const mapDispatchToProps = (dispatch: any, ownProps: ICommentComponentProps) =>
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: any) => {
|
||||
const { uid } = state.authorize
|
||||
const avatar = state.user.info && state.user.info[ownProps.comment.userId] ? state.user.info[ownProps.comment.userId].avatar || '' : ''
|
||||
const fullName = state.user.info && state.user.info[ownProps.comment.userId] ? state.user.info[ownProps.comment.userId].fullName || '' : ''
|
||||
const mapStateToProps = (state: any, ownProps: ICommentComponentProps) => {
|
||||
const commentOwnerId = ownProps.comment.userId
|
||||
const uid = state.getIn(['authorize', 'uid'])
|
||||
const avatar = ownProps.comment.userAvatar
|
||||
const fullName = ownProps.comment.userDisplayName
|
||||
return {
|
||||
translate: getTranslate(state.locale),
|
||||
translate: getTranslate(state.get('locale')),
|
||||
uid: uid,
|
||||
isCommentOwner: (uid === ownProps.comment.userId),
|
||||
info: state.user.info,
|
||||
isCommentOwner: (uid === commentOwnerId),
|
||||
commentOwner: state.getIn(['user', 'info', commentOwnerId]),
|
||||
avatar,
|
||||
fullName
|
||||
}
|
||||
|
||||
@@ -10,6 +10,11 @@ export interface ICommentComponentProps {
|
||||
*/
|
||||
comment: Comment
|
||||
|
||||
/**
|
||||
* Comment owner
|
||||
*/
|
||||
commentOwner?: Profile
|
||||
|
||||
/**
|
||||
* Open profile editor
|
||||
*
|
||||
@@ -63,14 +68,6 @@ export interface ICommentComponentProps {
|
||||
*/
|
||||
getUserInfo?: () => void
|
||||
|
||||
/**
|
||||
* User profile
|
||||
*
|
||||
* @type {{[userId: string]: Profile}}
|
||||
* @memberof ICommentComponentProps
|
||||
*/
|
||||
info?: {[userId: string]: Profile}
|
||||
|
||||
/**
|
||||
* User full name
|
||||
*
|
||||
@@ -95,6 +92,11 @@ export interface ICommentComponentProps {
|
||||
*/
|
||||
disableComments?: boolean
|
||||
|
||||
/**
|
||||
* Whether comment edit is open
|
||||
*/
|
||||
editorStatus: boolean
|
||||
|
||||
/**
|
||||
* Styles
|
||||
*/
|
||||
|
||||
@@ -45,4 +45,9 @@ export interface ICommentComponentState {
|
||||
* Wheter comment menu is open
|
||||
*/
|
||||
openMenu?: boolean
|
||||
|
||||
/**
|
||||
* Anchor element
|
||||
*/
|
||||
anchorEl: any
|
||||
}
|
||||
|
||||
@@ -6,23 +6,28 @@ import { NavLink } from 'react-router-dom'
|
||||
import { connect } from 'react-redux'
|
||||
import moment from 'moment/moment'
|
||||
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
|
||||
import { Map } from 'immutable'
|
||||
|
||||
import Paper from 'material-ui/Paper'
|
||||
import Button from 'material-ui/Button'
|
||||
import TextField from 'material-ui/TextField'
|
||||
import Divider from 'material-ui/Divider'
|
||||
import { ListItemIcon, ListItemText, ListItem } from 'material-ui/List'
|
||||
import { grey, teal } from 'material-ui/colors'
|
||||
import { LinearProgress } from 'material-ui/Progress'
|
||||
import { withStyles } from 'material-ui/styles'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import Divider from '@material-ui/core/Divider'
|
||||
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 { 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 { Card, CardActions, CardHeader, CardMedia, CardContent } from 'material-ui'
|
||||
import Grow from 'material-ui/transitions/Grow'
|
||||
import ClickAwayListener from 'material-ui/utils/ClickAwayListener'
|
||||
import { Card, CardActions, CardHeader, CardMedia, CardContent } from '@material-ui/core'
|
||||
import Grow from '@material-ui/core/Grow'
|
||||
import ClickAwayListener from '@material-ui/core/ClickAwayListener'
|
||||
import classNames from 'classnames'
|
||||
|
||||
// - Import actions
|
||||
import * as commentActions from 'actions/commentActions'
|
||||
import * as commentActions from 'store/actions/commentActions'
|
||||
|
||||
// - Import app components
|
||||
import CommentListComponent from 'components/commentList'
|
||||
@@ -30,11 +35,12 @@ import UserAvatar from 'components/userAvatar'
|
||||
|
||||
import { ICommentGroupComponentProps } from './ICommentGroupComponentProps'
|
||||
import { ICommentGroupComponentState } from './ICommentGroupComponentState'
|
||||
import { Comment } from 'core/domain/comments/comment'
|
||||
import { Comment } from 'core/domain/comments'
|
||||
import { ServerRequestModel } from 'models/server'
|
||||
import StringAPI from 'api/StringAPI'
|
||||
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) => ({
|
||||
textField: {
|
||||
@@ -210,8 +216,8 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
|
||||
* @return {DOM} list of comments' DOM
|
||||
*/
|
||||
commentList = () => {
|
||||
const {classes} = this.props
|
||||
let comments = this.props.commentSlides
|
||||
const {classes, postId} = this.props
|
||||
let comments = Map(this.props.commentSlides!).toJS()
|
||||
if (comments) {
|
||||
comments = _.fromPairs(_.toPairs(comments)
|
||||
.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])
|
||||
}
|
||||
return parsedComments.map((comment, index) => {
|
||||
const { userInfo } = this.props
|
||||
|
||||
const commentAvatar = userInfo && userInfo[comment.userId!] ? userInfo[comment.userId!].avatar || '' : ''
|
||||
const commentFullName = userInfo && userInfo[comment.userId!] ? userInfo[comment.userId!].fullName || '' : ''
|
||||
const commentAvatar = comment.userAvatar
|
||||
const commentFullName = comment.userDisplayName
|
||||
|
||||
const commentBody = (
|
||||
<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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@@ -314,20 +318,20 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
|
||||
</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' }}>
|
||||
<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>)
|
||||
: '')
|
||||
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 (
|
||||
<div key={postId + '-comments'}>
|
||||
<div style={commentSlides && Object.keys(commentSlides).length > 0 ? { display: 'block' } : { display: 'none' }}>
|
||||
<div key={postId + '-comments-group'}>
|
||||
<Divider />
|
||||
<div style={commentSlides && !commentSlides.isEmpty() ? { display: 'block' } : { display: 'none' }}>
|
||||
<Paper elevation={0} className='animate-top' style={!open ? { display: 'block' } : { display: 'none' }}>
|
||||
|
||||
<div style={{ position: 'relative', height: '60px' }} >
|
||||
@@ -339,11 +343,11 @@ export class CommentGroupComponent extends Component<ICommentGroupComponentProps
|
||||
</div>
|
||||
</div>
|
||||
</Paper>
|
||||
|
||||
</div>
|
||||
{
|
||||
open ? loadComments : ''
|
||||
}
|
||||
|
||||
</div>
|
||||
{
|
||||
(!this.props.disableComments && open )
|
||||
? commentWriteBox
|
||||
@@ -377,19 +381,20 @@ const mapDispatchToProps = (dispatch: any, ownProps: ICommentGroupComponentProps
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: ICommentGroupComponentProps) => {
|
||||
const { post, user, authorize, server } = state
|
||||
const {request} = server
|
||||
const mapStateToProps = (state: Map<string, any>, ownProps: ICommentGroupComponentProps) => {
|
||||
const { ownerPostUserId, postId } = ownProps
|
||||
const commentSlides = post.userPosts[ownerPostUserId] && post.userPosts[ownerPostUserId][postId] ? post.userPosts[ownerPostUserId][postId].comments : {}
|
||||
const getCommentsRequest: ServerRequestModel = request ? request[StringAPI.createServerRequestId(ServerRequestType.CommentGetComments, postId)] : null
|
||||
const uid = state.getIn(['authorize', 'uid'], 0)
|
||||
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 {
|
||||
translate: getTranslate(state.locale),
|
||||
getCommentsRequest,
|
||||
translate: getTranslate(state.get('locale')),
|
||||
commentsRequestStatus : commentsRequestStatus ? commentsRequestStatus.status : ServerRequestStatusType.NoAction,
|
||||
commentSlides,
|
||||
avatar: user.info && user.info[state.authorize.uid] ? user.info[authorize.uid].avatar || '' : '',
|
||||
fullName: user.info && user.info[state.authorize.uid] ? user.info[authorize.uid].fullName || '' : '',
|
||||
userInfo: user.info
|
||||
avatar: user.avatar || '',
|
||||
fullName: user.fullName || '',
|
||||
userInfo: state.getIn(['user', 'info'])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Profile } from 'core/domain/users'
|
||||
import { Comment } from 'core/domain/comments'
|
||||
import { ServerRequestModel } from 'models/server'
|
||||
import { ServerRequestStatusType } from 'store/actions/serverRequestStatusType'
|
||||
import {Map} from 'immutable'
|
||||
export interface ICommentGroupComponentProps {
|
||||
|
||||
/**
|
||||
@@ -9,15 +11,12 @@ export interface ICommentGroupComponentProps {
|
||||
* @type {{[commentId: string]: Comment}}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
comments?: {[commentId: string]: Comment}
|
||||
comments?: Map<string, Comment>
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -33,7 +32,7 @@ export interface ICommentGroupComponentProps {
|
||||
* @type {{[userId: string]: Profile}}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
userInfo?: {[userId: string]: Profile}
|
||||
userInfo?: Map<string, Profile>
|
||||
|
||||
/**
|
||||
* Comment group is open {true} or not {false}
|
||||
@@ -102,7 +101,7 @@ export interface ICommentGroupComponentProps {
|
||||
/**
|
||||
* Get post comments request payload
|
||||
*/
|
||||
getCommentsRequest?: ServerRequestModel
|
||||
commentsRequestStatus?: ServerRequestStatusType
|
||||
|
||||
/**
|
||||
* Styles
|
||||
|
||||
@@ -2,7 +2,16 @@
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
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 CommentComponent from 'components/comment'
|
||||
@@ -15,6 +24,15 @@ import { Comment } from 'core/domain/comments'
|
||||
|
||||
// - Import actions
|
||||
|
||||
const styles = (theme: any) => ({
|
||||
list: {
|
||||
width: '100%',
|
||||
maxHeight: 290,
|
||||
overflowY: 'auto',
|
||||
overflowX: 'visible'
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
*/
|
||||
@@ -54,21 +72,30 @@ export class CommentListComponent extends Component<ICommentListComponentProps,
|
||||
* @return {DOM} list of comments' DOM
|
||||
*/
|
||||
commentList = () => {
|
||||
let comments = this.props.comments
|
||||
if (comments) {
|
||||
let comments = Map<string, Comment>(this.props.comments)
|
||||
let commentsEditorStatus = Map<string, boolean>(this.props.commentsEditorStatus!)
|
||||
if (!comments.isEmpty()) {
|
||||
|
||||
let parsedComments: Comment[] = []
|
||||
Object.keys(comments).forEach((commentId) => {
|
||||
comments.forEach((comment, commentId) => {
|
||||
parsedComments.push({
|
||||
id: commentId,
|
||||
...comments[commentId]
|
||||
...Map(comment!).toJS()
|
||||
})
|
||||
})
|
||||
let sortedComments = PostAPI.sortObjectsDate(parsedComments)
|
||||
|
||||
return sortedComments.map((comment: Comment, index: number, array: Comment) => {
|
||||
|
||||
return <CommentComponent key={comment.id!} comment={comment} isPostOwner={this.props.isPostOwner} disableComments={this.props.disableComments}/>
|
||||
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
|
||||
*/
|
||||
render () {
|
||||
|
||||
const styles: any = {
|
||||
list: {
|
||||
width: '100%',
|
||||
maxHeight: 450
|
||||
}
|
||||
}
|
||||
const {classes, postId} = this.props
|
||||
|
||||
return (
|
||||
|
||||
<List style={styles.list}>
|
||||
<List key={`comment-list-${postId}`} className={classes.list}>
|
||||
|
||||
{this.commentList()}
|
||||
</List>
|
||||
@@ -116,11 +137,12 @@ const mapDispatchToProps = (dispatch: any, ownProps: ICommentListComponentProps)
|
||||
* @param {object} ownProps is the props belong to 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 {
|
||||
|
||||
commentsEditorStatus
|
||||
}
|
||||
}
|
||||
|
||||
// - 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 {Map} from 'immutable'
|
||||
|
||||
export interface ICommentListComponentProps {
|
||||
|
||||
@@ -8,7 +9,12 @@ export interface ICommentListComponentProps {
|
||||
* @type {{[commentId: string]: Comment}}
|
||||
* @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
|
||||
@@ -18,6 +24,11 @@ export interface ICommentListComponentProps {
|
||||
*/
|
||||
isPostOwner: boolean
|
||||
|
||||
/**
|
||||
* The post identifier comments belong to
|
||||
*/
|
||||
postId: string
|
||||
|
||||
/**
|
||||
* Comment on the post is disabled {false} or not {true}
|
||||
*
|
||||
@@ -25,4 +36,9 @@ export interface ICommentListComponentProps {
|
||||
* @memberof ICommentListComponentProps
|
||||
*/
|
||||
disableComments: boolean
|
||||
|
||||
/**
|
||||
* Styles
|
||||
*/
|
||||
classes?: any
|
||||
}
|
||||
|
||||
@@ -3,42 +3,56 @@ import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
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 IconButton from 'material-ui/IconButton'
|
||||
import MoreVertIcon from 'material-ui-icons/MoreVert'
|
||||
import SvgCamera from 'material-ui-icons/PhotoCamera'
|
||||
import List, { ListItem, ListItemIcon, ListItemText } from 'material-ui/List'
|
||||
import Menu, { MenuList, MenuItem } from 'material-ui/Menu'
|
||||
import Button from 'material-ui/Button'
|
||||
import RaisedButton from 'material-ui/Button'
|
||||
import { grey } from '@material-ui/core/colors'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert'
|
||||
import SvgCamera from '@material-ui/icons/PhotoCamera'
|
||||
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 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 Dialog, {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle
|
||||
} from 'material-ui/Dialog'
|
||||
import Divider from 'material-ui/Divider'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import TextField from 'material-ui/TextField'
|
||||
import Input, { InputLabel } from 'material-ui/Input'
|
||||
import { FormControl, FormHelperText } from 'material-ui/Form'
|
||||
import { withStyles } from 'material-ui/styles'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||
import Divider from '@material-ui/core/Divider'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import Input from '@material-ui/core/Input'
|
||||
import InputLabel from '@material-ui/core/InputLabel'
|
||||
import FormHelperText from '@material-ui/core/FormHelperText'
|
||||
import FormControl from '@material-ui/core/FormControl'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
// - Import app components
|
||||
import ImgCover from 'components/imgCover'
|
||||
import UserAvatarComponent from 'components/userAvatar'
|
||||
import ImageGallery from 'components/imageGallery'
|
||||
import AppDialogTitle from 'layouts/dialogTitle'
|
||||
import AppInput from 'layouts/appInput'
|
||||
|
||||
// - Import API
|
||||
import FileAPI from 'api/FileAPI'
|
||||
|
||||
// - Import actions
|
||||
import * as userActions from 'actions/userActions'
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as imageGalleryActions from 'actions/imageGalleryActions'
|
||||
import * as userActions from 'store/actions/userActions'
|
||||
import * as globalActions from 'store/actions/globalActions'
|
||||
import * as imageGalleryActions from 'store/actions/imageGalleryActions'
|
||||
|
||||
import { IEditProfileComponentProps } from './IEditProfileComponentProps'
|
||||
import { IEditProfileComponentState } from './IEditProfileComponentState'
|
||||
@@ -47,6 +61,48 @@ import { Profile } from 'core/domain/users'
|
||||
const styles = (theme: any) => ({
|
||||
dialogTitle: {
|
||||
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: {
|
||||
marginLeft: '10px'
|
||||
},
|
||||
box: {
|
||||
padding: '0px 24px 20px 24px',
|
||||
display: 'flex'
|
||||
|
||||
},
|
||||
dialogGallery: {
|
||||
width: '',
|
||||
maxWidth: '530px',
|
||||
@@ -156,7 +207,27 @@ export class EditProfileComponent extends Component<IEditProfileComponentProps,
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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({
|
||||
fullNameInputError: 'This field is required'
|
||||
})
|
||||
@@ -239,12 +311,16 @@ export class EditProfileComponent extends Component<IEditProfileComponentProps,
|
||||
fullNameInputError: ''
|
||||
})
|
||||
|
||||
this.props.update!({
|
||||
update!({
|
||||
fullName: fullNameInput,
|
||||
tagLine: tagLineInput,
|
||||
avatar: avatar,
|
||||
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() {
|
||||
this.handleResize(null)
|
||||
}
|
||||
@@ -293,7 +376,8 @@ export class EditProfileComponent extends Component<IEditProfileComponentProps,
|
||||
*/
|
||||
render() {
|
||||
|
||||
const { classes, translate } = this.props
|
||||
const { classes, translate, currentLanguage } = this.props
|
||||
const { defaultBirthday, webUrl, twitterId, companyName } = this.state
|
||||
const iconButtonElement = (
|
||||
<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' />
|
||||
@@ -314,12 +398,13 @@ export class EditProfileComponent extends Component<IEditProfileComponentProps,
|
||||
<div>
|
||||
{/* Edit profile dialog */}
|
||||
<Dialog
|
||||
PaperProps={{ className: classes.fullPageXs }}
|
||||
key='Edit-Profile'
|
||||
open={this.props.open!}
|
||||
onClose={this.props.onRequestClose}
|
||||
maxWidth='sm'
|
||||
>
|
||||
<DialogContent>
|
||||
<DialogContent className={classes.dialogContentRoot}>
|
||||
{/* Banner */}
|
||||
<div style={{ position: 'relative' }}>
|
||||
<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*/}
|
||||
<Paper style={this.styles.paper} elevation={1}>
|
||||
<div style={this.styles.title as any}>{translate!('profile.personalInformationLabel')}</div>
|
||||
<div style={this.styles.box}>
|
||||
<FormControl aria-describedby='fullNameInputError'>
|
||||
<div className={classes.box}>
|
||||
<FormControl fullWidth aria-describedby='fullNameInputError'>
|
||||
<InputLabel htmlFor='fullNameInput'>{translate!('profile.fullName')}</InputLabel>
|
||||
<Input id='fullNameInput'
|
||||
onChange={this.handleInputChange}
|
||||
name='fullNameInput'
|
||||
value={this.state.fullNameInput} />
|
||||
value={this.state.fullNameInput}
|
||||
/>
|
||||
<FormHelperText id='fullNameInputError'>{this.state.fullNameInputError}</FormHelperText>
|
||||
</FormControl>
|
||||
</div>
|
||||
<br />
|
||||
<div style={this.styles.box}>
|
||||
<FormControl aria-describedby='tagLineInputError'>
|
||||
<div className={classes.box}>
|
||||
<FormControl fullWidth aria-describedby='tagLineInputError'>
|
||||
<InputLabel htmlFor='tagLineInput'>{translate!('profile.tagline')}</InputLabel>
|
||||
<Input id='tagLineInput'
|
||||
onChange={this.handleInputChange}
|
||||
name='tagLineInput'
|
||||
value={this.state.tagLineInput} />
|
||||
value={this.state.tagLineInput}
|
||||
/>
|
||||
<FormHelperText id='tagLineInputError'>{this.state.fullNameInputError}</FormHelperText>
|
||||
</FormControl>
|
||||
</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 />
|
||||
|
||||
</Paper>
|
||||
<div style={{ height: '16px' }}></div>
|
||||
<div className={classes.bottomPaperSpace}></div>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<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>
|
||||
</DialogActions>
|
||||
<DialogActions className={classes.fixedDownStickyXS}>
|
||||
<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>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
{/* Image gallery for banner*/}
|
||||
<Dialog
|
||||
PaperProps={{ className: classes.fullPageXs }}
|
||||
open={this.state.openBanner}
|
||||
onClose={this.handleCloseBannerGallery}
|
||||
|
||||
@@ -400,6 +533,7 @@ export class EditProfileComponent extends Component<IEditProfileComponentProps,
|
||||
|
||||
{/* Image gallery for avatar */}
|
||||
<Dialog
|
||||
PaperProps={{ className: classes.fullPageXs }}
|
||||
open={this.state.openAvatar}
|
||||
onClose={this.handleCloseAvatarGallery}
|
||||
>
|
||||
@@ -434,15 +568,17 @@ const mapDispatchToProps = (dispatch: any, ownProps: IEditProfileComponentProps)
|
||||
* @param {object} ownProps is the props belong to 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 {
|
||||
translate: getTranslate(state.locale),
|
||||
open: state.user.openEditProfile,
|
||||
info: state.user.info[state.authorize.uid],
|
||||
avatarURL: state.imageGallery.imageURLList
|
||||
currentLanguage: getActiveLanguage(state.get('locale')).code,
|
||||
translate: getTranslate(state.get('locale')),
|
||||
open: state.getIn(['user', 'openEditProfile'], false),
|
||||
info: state.getIn(['user', 'info', uid]),
|
||||
avatarURL: state.getIn(['imageGallery', 'imageURLList'])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - 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?: (state: any) => any
|
||||
|
||||
/**
|
||||
* Current locale language
|
||||
*/
|
||||
currentLanguage?: string
|
||||
}
|
||||
|
||||
@@ -65,4 +65,29 @@ export interface IEditProfileComponentState {
|
||||
*/
|
||||
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 { connect } from 'react-redux'
|
||||
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 { getTranslate, getActiveLanguage } from 'react-localize-redux'
|
||||
import {Map} from 'immutable'
|
||||
|
||||
// - Import app components
|
||||
import UserBoxList from 'components/userBoxList'
|
||||
@@ -13,9 +14,10 @@ import LoadMoreProgressComponent from 'layouts/loadMoreProgress'
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as userActions from 'actions/userActions'
|
||||
import * as userActions from 'store/actions/userActions'
|
||||
import { IFindPeopleComponentProps } from './IFindPeopleComponentProps'
|
||||
import { IFindPeopleComponentState } from './IFindPeopleComponentState'
|
||||
import { UserTie } from 'core/domain/circles/userTie'
|
||||
|
||||
/**
|
||||
* Create component class
|
||||
@@ -50,7 +52,7 @@ export class FindPeopleComponent extends Component<IFindPeopleComponentProps, IF
|
||||
*/
|
||||
render () {
|
||||
const {hasMorePeople, translate} = this.props
|
||||
|
||||
const peopleInfo = Map<string, UserTie>(this.props.peopleInfo!)
|
||||
return (
|
||||
<div>
|
||||
<InfiniteScroll
|
||||
@@ -63,11 +65,11 @@ export class FindPeopleComponent extends Component<IFindPeopleComponentProps, IF
|
||||
|
||||
<div className='tracks'>
|
||||
|
||||
{this.props.peopleInfo && Object.keys(this.props.peopleInfo).length !== 0 ? (<div>
|
||||
{peopleInfo && peopleInfo.count() > 0 ? (<div>
|
||||
<div className='profile__title'>
|
||||
{translate!('people.suggestionsForYouLabel')}
|
||||
</div>
|
||||
<UserBoxList users={this.props.peopleInfo}/>
|
||||
<UserBoxList users={peopleInfo}/>
|
||||
<div style={{ height: '24px' }}></div>
|
||||
</div>) : (<div className='g__title-center'>
|
||||
{translate!('people.nothingToShowLabel')}
|
||||
@@ -98,11 +100,13 @@ const mapDispatchToProps = (dispatch: any, ownProps: IFindPeopleComponentProps)
|
||||
* @return {object} props of component
|
||||
*/
|
||||
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 {
|
||||
translate: getTranslate(state.locale),
|
||||
translate: getTranslate(state.get('locale')),
|
||||
peopleInfo: info,
|
||||
hasMorePeople: people.hasMoreData
|
||||
hasMorePeople
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Profile } from 'core/domain/users/profile'
|
||||
import { UserTie } from 'core/domain/circles'
|
||||
|
||||
export interface IFindPeopleComponentProps {
|
||||
|
||||
@@ -11,11 +12,8 @@ export interface IFindPeopleComponentProps {
|
||||
|
||||
/**
|
||||
* 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}
|
||||
|
||||
@@ -3,6 +3,7 @@ import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
|
||||
import {Map} from 'immutable'
|
||||
|
||||
// - Import app components
|
||||
import UserBoxList from 'components/userBoxList'
|
||||
@@ -46,13 +47,14 @@ export class FollowersComponent extends Component<IFollowersComponentProps,IFoll
|
||||
*/
|
||||
render () {
|
||||
const {translate} = this.props
|
||||
const followers = this.props.followers!
|
||||
return (
|
||||
<div>
|
||||
{(this.props.followers && Object.keys(this.props.followers).length !== 0) ? (<div>
|
||||
{(followers && followers.keySeq().count() !== 0) ? (<div>
|
||||
<div className='profile__title'>
|
||||
{translate!('people.followersLabel')}
|
||||
</div>
|
||||
<UserBoxList users={this.props.followers} />
|
||||
<UserBoxList users={followers} />
|
||||
<div style={{ height: '24px' }}></div>
|
||||
</div>)
|
||||
: (<div className='g__title-center'>
|
||||
@@ -81,13 +83,13 @@ const mapDispatchToProps = (dispatch: any,ownProps: IFollowersComponentProps) =>
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any,ownProps: IFollowersComponentProps) => {
|
||||
const {circle, authorize, server} = state
|
||||
const { uid } = state.authorize
|
||||
const circles: { [circleId: string]: Circle } = circle ? (circle.circleList || {}) : {}
|
||||
const followers = circle ? circle.userTieds : {}
|
||||
const mapStateToProps = (state: Map<string, any>,ownProps: IFollowersComponentProps) => {
|
||||
|
||||
const uid = state.getIn(['authorize', 'uid'], 0)
|
||||
const circles: { [circleId: string]: Circle } = state.getIn(['circle', 'circleList'], {})
|
||||
const followers = state.getIn(['circle', 'userTieds'], {})
|
||||
return{
|
||||
translate: getTranslate(state.locale),
|
||||
translate: getTranslate(state.get('locale')),
|
||||
followers
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { UserTie } from 'core/domain/circles'
|
||||
import {Map} from 'immutable'
|
||||
|
||||
export interface IFollowersComponentProps {
|
||||
|
||||
/**
|
||||
* User followers info
|
||||
*
|
||||
* @type {{[userId: string]: UserTie}}
|
||||
* @memberof IFindPeopleComponentProps
|
||||
*/
|
||||
followers?: {[userId: string]: UserTie}
|
||||
followers?: Map<string, UserTie>
|
||||
|
||||
/**
|
||||
* Translate to locale string
|
||||
|
||||
@@ -3,6 +3,7 @@ import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
|
||||
import {Map} from 'immutable'
|
||||
|
||||
// - Import app components
|
||||
import UserBoxList from 'components/userBoxList'
|
||||
@@ -46,13 +47,14 @@ export class FollowingComponent extends Component<IFollowingComponentProps,IFoll
|
||||
*/
|
||||
render () {
|
||||
const {translate} = this.props
|
||||
const followingUsers = Map(this.props.followingUsers!)
|
||||
return (
|
||||
<div>
|
||||
{(this.props.followingUsers && Object.keys(this.props.followingUsers).length !== 0 ) ? (<div>
|
||||
{(followingUsers && followingUsers.keySeq().count() !== 0 ) ? (<div>
|
||||
<div className='profile__title'>
|
||||
{translate!('people.followingLabel')}
|
||||
</div>
|
||||
<UserBoxList users={this.props.followingUsers} />
|
||||
<UserBoxList users={followingUsers} />
|
||||
<div style={{ height: '24px' }}></div>
|
||||
|
||||
</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
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any,ownProps: IFollowingComponentProps) => {
|
||||
const {circle, authorize, server} = state
|
||||
const { uid } = state.authorize
|
||||
const circles: { [circleId: string]: Circle } = circle ? (circle.circleList || {}) : {}
|
||||
const followingUsers = circle ? circle.userTies : {}
|
||||
const mapStateToProps = (state: Map<string, any>,ownProps: IFollowingComponentProps) => {
|
||||
|
||||
const uid = state.getIn(['authorize', 'uid'], 0)
|
||||
const circles: { [circleId: string]: Circle } = state.getIn(['circle', 'circleList'], {})
|
||||
const followingUsers = state.getIn(['circle', 'userTies'], {})
|
||||
return {
|
||||
translate: getTranslate(state.locale),
|
||||
translate: getTranslate(state.get('locale')),
|
||||
uid,
|
||||
circles,
|
||||
followingUsers
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { UserTie } from 'core/domain/circles'
|
||||
import {Map} from 'immutable'
|
||||
|
||||
export interface IFollowingComponentProps {
|
||||
|
||||
followingUsers?: {[userId: string]: UserTie}
|
||||
followingUsers?: Map<string, UserTie>
|
||||
|
||||
/**
|
||||
* 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 { NavLink } from 'react-router-dom'
|
||||
import { connect } from 'react-redux'
|
||||
import SvgDehaze from 'material-ui-icons/Dehaze'
|
||||
import { grey, blue } from 'material-ui/colors'
|
||||
import Toolbar from 'material-ui/Toolbar'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import Popover from 'material-ui/Popover'
|
||||
import AppBar from 'material-ui/AppBar'
|
||||
import Menu, { MenuList, MenuItem } from 'material-ui/Menu'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import NotificationsIcon from 'material-ui-icons/Notifications'
|
||||
import EventListener, { withOptions } from 'react-event-listener'
|
||||
import Tooltip from 'material-ui/Tooltip'
|
||||
import Typography from 'material-ui/Typography'
|
||||
import classNames from 'classnames'
|
||||
import { Map } from 'immutable'
|
||||
|
||||
// - Material UI
|
||||
import SvgDehaze from '@material-ui/icons/Dehaze'
|
||||
import { grey, blue } from '@material-ui/core/colors'
|
||||
import Toolbar from '@material-ui/core/Toolbar'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import Popover from '@material-ui/core/Popover'
|
||||
import AppBar from '@material-ui/core/AppBar'
|
||||
import MenuList from '@material-ui/core/MenuList'
|
||||
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 { withStyles } from 'material-ui/styles'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
|
||||
import config from 'src/config'
|
||||
|
||||
@@ -24,8 +30,8 @@ import UserAvatarComponent from 'components/userAvatar'
|
||||
import Notify from 'components/notify'
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import { authorizeActions } from 'actions'
|
||||
import * as globalActions from 'store/actions/globalActions'
|
||||
import { authorizeActions } from 'store/actions'
|
||||
import { IHomeHeaderComponentProps } from './IHomeHeaderComponentProps'
|
||||
import { IHomeHeaderComponentState } from './IHomeHeaderComponentState'
|
||||
|
||||
@@ -92,13 +98,8 @@ export class HomeHeaderComponent extends Component<IHomeHeaderComponentProps, IH
|
||||
|
||||
// On click toggle sidebar
|
||||
onToggleSidebar = () => {
|
||||
if (this.props.sidebarStatus) {
|
||||
this.props.sidebar!(false, 'onToggle')
|
||||
|
||||
} else {
|
||||
this.props.sidebar!(true, 'onToggle')
|
||||
|
||||
}
|
||||
const {onToggleDrawer} = this.props
|
||||
onToggleDrawer()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -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
|
||||
* @param {event} evt is the event is passed by winodw resize event
|
||||
*/
|
||||
handleResize = (event: any) => {
|
||||
|
||||
const {drawerStatus} = this.props
|
||||
// Set initial state
|
||||
let width = window.innerWidth
|
||||
|
||||
if (width >= 600 && !this.state.showTitle) {
|
||||
this.setState({
|
||||
showTitle: true
|
||||
})
|
||||
if (width >= 600 && !drawerStatus) {
|
||||
this.onToggleSidebar()
|
||||
} 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 () {
|
||||
const { classes , translate} = this.props
|
||||
const { classes , translate, theme} = this.props
|
||||
const anchor = theme.direction === 'rtl' ? 'right' : 'left'
|
||||
return (
|
||||
|
||||
<AppBar position='fixed' color='secondary'>
|
||||
<Toolbar>
|
||||
<EventListener
|
||||
target='window'
|
||||
onResize={this.handleResize}
|
||||
onKeyUp={this.handleKeyUp}
|
||||
/>
|
||||
{/* Left side */}
|
||||
|
||||
<IconButton onClick={this.onToggleSidebar} >
|
||||
@@ -205,7 +192,9 @@ export class HomeHeaderComponent extends Component<IHomeHeaderComponentProps, IH
|
||||
{config.settings.appName}
|
||||
</Typography>
|
||||
<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>
|
||||
|
||||
{/* Notification */}
|
||||
@@ -270,21 +259,23 @@ const mapDispatchToProps = (dispatch: Function, ownProps: IHomeHeaderComponentPr
|
||||
}
|
||||
|
||||
// - Map state to props
|
||||
const mapStateToProps = (state: any, ownProps: IHomeHeaderComponentProps) => {
|
||||
const mapStateToProps = (state: Map<string,any>, ownProps: IHomeHeaderComponentProps) => {
|
||||
|
||||
let notifyCount = state.notify.userNotifies
|
||||
? Object
|
||||
.keys(state.notify.userNotifies)
|
||||
.filter((key) => !state.notify.userNotifies[key].isSeen).length
|
||||
const uid = state.getIn(['authorize', 'uid'], 0)
|
||||
const userNotifies: Map<string, any> = state.getIn(['notify','userNotifies'])
|
||||
let notifyCount = userNotifies
|
||||
? userNotifies
|
||||
.filter((notification) => !notification.get('isSeen', false)).count()
|
||||
: 0
|
||||
const user = state.getIn(['user', 'info', uid], {})
|
||||
return {
|
||||
translate: getTranslate(state.locale),
|
||||
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : '',
|
||||
fullName: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].fullName : '',
|
||||
title: state.global.headerTitle,
|
||||
translate: getTranslate(state.get('locale')),
|
||||
avatar: user.avatar || '',
|
||||
fullName: user.fullName || '',
|
||||
title: state.getIn(['global', 'headerTitle'], ''),
|
||||
notifyCount
|
||||
}
|
||||
}
|
||||
|
||||
// - 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}
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
sidebarStatus?: boolean
|
||||
drawerStatus: boolean
|
||||
|
||||
/**
|
||||
* Logout user
|
||||
@@ -59,12 +59,17 @@ export interface IHomeHeaderComponentProps {
|
||||
*
|
||||
* @memberof IHomeHeaderComponentProps
|
||||
*/
|
||||
sidebar?: (status: boolean, source: string) => void
|
||||
onToggleDrawer: () => void
|
||||
|
||||
/**
|
||||
* Material ui theme style
|
||||
*/
|
||||
classes?: any
|
||||
|
||||
/**
|
||||
* Theme
|
||||
*/
|
||||
theme?: any
|
||||
|
||||
/**
|
||||
* Translate to locale string
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Image } from 'core/domain/imageGallery'
|
||||
import {Map, Collection, List} from 'immutable'
|
||||
|
||||
export interface IImageGalleryComponentProps {
|
||||
|
||||
@@ -33,11 +34,13 @@ export interface IImageGalleryComponentProps {
|
||||
|
||||
/**
|
||||
* List of image in image gallery
|
||||
*
|
||||
* @type {Image[]}
|
||||
* @memberof IImageGalleryComponentProps
|
||||
*/
|
||||
images?: Image[]
|
||||
images?: List<Image>
|
||||
|
||||
/**
|
||||
* Styles
|
||||
*/
|
||||
classes?: any
|
||||
|
||||
/**
|
||||
* Translate to locale string
|
||||
|
||||
@@ -2,20 +2,24 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import GridList, { GridListTile, GridListTileBar } from 'material-ui/GridList'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import StarBorder from 'material-ui-icons/StarBorder'
|
||||
import Button from 'material-ui/Button'
|
||||
import SvgUpload from 'material-ui-icons/CloudUpload'
|
||||
import SvgAddImage from 'material-ui-icons/AddAPhoto'
|
||||
import SvgDelete from 'material-ui-icons/Delete'
|
||||
import { grey } from 'material-ui/colors'
|
||||
import GridList from '@material-ui/core/GridList'
|
||||
import GridListTileBar from '@material-ui/core/GridListTileBar'
|
||||
import GridListTile from '@material-ui/core/GridListTile'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import StarBorder from '@material-ui/icons/StarBorder'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import SvgUpload from '@material-ui/icons/CloudUpload'
|
||||
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 { getTranslate, getActiveLanguage } from 'react-localize-redux'
|
||||
import {Map} from 'immutable'
|
||||
|
||||
// - Import actions
|
||||
import * as imageGalleryActions from 'actions/imageGalleryActions'
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as imageGalleryActions from 'store/actions/imageGalleryActions'
|
||||
import * as globalActions from 'store/actions/globalActions'
|
||||
|
||||
// - Import app components
|
||||
import Img from 'components/img'
|
||||
@@ -26,6 +30,17 @@ import { IImageGalleryComponentProps } from './IImageGalleryComponentProps'
|
||||
import { IImageGalleryComponentState } from './IImageGalleryComponentState'
|
||||
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
|
||||
*/
|
||||
@@ -149,7 +164,6 @@ export class ImageGalleryComponent extends Component<IImageGalleryComponentProps
|
||||
}
|
||||
|
||||
imageList = () => {
|
||||
|
||||
return this.props.images!.map((image: Image, index) => {
|
||||
|
||||
return (
|
||||
@@ -242,14 +256,16 @@ const mapDispatchToProps = (dispatch: any, ownProps: IImageGalleryComponentProps
|
||||
* @param {object} ownProps is the props belong to 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 {
|
||||
translate: getTranslate(state.locale),
|
||||
images: state.imageGallery.images,
|
||||
avatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : ''
|
||||
translate: getTranslate(state.get('locale')),
|
||||
images: state.getIn(['imageGallery', 'images']),
|
||||
avatar: currentUser ? currentUser.avatar : ''
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// - 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 PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import SvgImage from 'material-ui-icons/Image'
|
||||
import { withStyles } from 'material-ui/styles'
|
||||
import SvgImage from '@material-ui/icons/Image'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
|
||||
import { Map } from 'immutable'
|
||||
|
||||
// - Import app components
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as imageGalleryActions from 'actions/imageGalleryActions'
|
||||
import * as imageGalleryActions from 'store/actions/imageGalleryActions'
|
||||
import { IImgComponentProps } from './IImgComponentProps'
|
||||
import { IImgComponentState } from './IImgComponentState'
|
||||
|
||||
@@ -121,13 +122,13 @@ const mapDispatchToProps = (dispatch: any, ownProps: IImgComponentProps) => {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: IImgComponentProps) => {
|
||||
const mapStateToProps = (state: Map<string, any>, ownProps: IImgComponentProps) => {
|
||||
return {
|
||||
translate: getTranslate(state.locale),
|
||||
avatarURL: state.imageGallery.imageURLList,
|
||||
imageRequests: state.imageGallery.imageRequests
|
||||
translate: getTranslate(state.get('locale')),
|
||||
avatarURL: state.getIn(['imageGallery', 'imageURLList']),
|
||||
imageRequests: state.getIn(['imageGallery', 'imageRequests'])
|
||||
}
|
||||
}
|
||||
|
||||
// - 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 PropTypes from 'prop-types'
|
||||
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 {Map} from 'immutable'
|
||||
|
||||
// - Import app components
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as imageGalleryActions from 'actions/imageGalleryActions'
|
||||
import * as imageGalleryActions from 'store/actions/imageGalleryActions'
|
||||
import { IImgCoverComponentProps } from './IImgCoverComponentProps'
|
||||
import { IImgCoverComponentState } from './IImgCoverComponentState'
|
||||
|
||||
@@ -153,9 +154,7 @@ const mapDispatchToProps = (dispatch: any, ownProps: IImgCoverComponentProps) =>
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: IImgCoverComponentProps) => {
|
||||
return {
|
||||
translate: getTranslate(state.locale),
|
||||
avatarURL: state.imageGallery.imageURLList,
|
||||
imageRequests: state.imageGallery.imageRequests
|
||||
translate: getTranslate(state.get('locale'))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
/**
|
||||
* Loading is active {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof IMasterLoadingComponentProps
|
||||
*/
|
||||
activeLoading: boolean
|
||||
|
||||
handleLoading: (status: boolean) => void
|
||||
error?: boolean
|
||||
timedOut?: boolean
|
||||
pastDelay?: boolean
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { CircularProgress } from 'material-ui/Progress'
|
||||
import Dialog from 'material-ui/Dialog'
|
||||
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import red from '@material-ui/core/colors/red'
|
||||
import { IMasterLoadingComponentProps } from './IMasterLoadingComponentProps'
|
||||
import { IMasterLoadingComponentState } from './IMasterLoadingComponentState'
|
||||
import Grid from '@material-ui/core/Grid/Grid'
|
||||
import { Typography } from '@material-ui/core'
|
||||
|
||||
// - Import app components
|
||||
|
||||
@@ -11,26 +14,78 @@ import { IMasterLoadingComponentState } from './IMasterLoadingComponentState'
|
||||
export default class MasterLoadingComponent extends Component<IMasterLoadingComponentProps, IMasterLoadingComponentState> {
|
||||
|
||||
// Constructor
|
||||
constructor (props: IMasterLoadingComponentProps) {
|
||||
constructor(props: IMasterLoadingComponentProps) {
|
||||
super(props)
|
||||
// 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 () {
|
||||
const {activeLoading} = this.props
|
||||
render() {
|
||||
return (
|
||||
|
||||
<div className='mLoading__loading' style={{ display: (activeLoading ? 'flex' : 'none') }}>
|
||||
<CircularProgress
|
||||
color='secondary'
|
||||
size={50}
|
||||
variant='determinate'
|
||||
value={25}
|
||||
min={0}
|
||||
max={50}
|
||||
/>
|
||||
<div className='mLoading__loading'>
|
||||
{
|
||||
this.loadProgress()
|
||||
}
|
||||
|
||||
</div>
|
||||
|
||||
)
|
||||
|
||||
@@ -1,44 +1,30 @@
|
||||
import { Profile } from 'core/domain/users'
|
||||
import { Notification } from 'core/domain/notifications'
|
||||
|
||||
import {Map} from 'immutable'
|
||||
export interface INotifyComponentProps {
|
||||
|
||||
/**
|
||||
* Notifications
|
||||
*
|
||||
* @type {{[notificationId: string]: Notification}}
|
||||
* @memberof INotifyComponentProps
|
||||
*/
|
||||
notifications?: {[notificationId: string]: Notification}
|
||||
notifications?: Map<string, any>
|
||||
|
||||
/**
|
||||
* Users' profile
|
||||
*
|
||||
* @type {{[userId: string]: Profile}}
|
||||
* @memberof INotifyComponentProps
|
||||
*/
|
||||
info?: {[userId: string]: Profile}
|
||||
info?: Map<string, Profile>
|
||||
|
||||
/**
|
||||
* Close notification
|
||||
*
|
||||
* @memberof INotifyComponentProps
|
||||
*/
|
||||
onRequestClose: () => void
|
||||
|
||||
/**
|
||||
* User notifications popover is opem {true} or not {false}
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof INotifyComponentProps
|
||||
*/
|
||||
open: boolean
|
||||
|
||||
/**
|
||||
* Keep element
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof INotifyComponentProps
|
||||
*/
|
||||
anchorEl?: any
|
||||
|
||||
|
||||
@@ -4,19 +4,25 @@ import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import classNames from 'classnames'
|
||||
import { Manager, Target, Popper } from 'react-popper'
|
||||
import ClickAwayListener from 'material-ui/utils/ClickAwayListener'
|
||||
import Grow from 'material-ui/transitions/Grow'
|
||||
import { withStyles } from 'material-ui/styles'
|
||||
import Typography from 'material-ui/Typography'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import List, { ListItem, ListItemSecondaryAction, ListItemText } from 'material-ui/List'
|
||||
import ClickAwayListener from '@material-ui/core/ClickAwayListener'
|
||||
import Grow from '@material-ui/core/Grow'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
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 NotifyItem from 'components/notifyItem'
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as userActions from 'actions/userActions'
|
||||
import * as userActions from 'store/actions/userActions'
|
||||
|
||||
import { INotifyComponentProps } from './INotifyComponentProps'
|
||||
import { INotifyComponentState } from './INotifyComponentState'
|
||||
@@ -52,8 +58,17 @@ const styles = (theme: any) => ({
|
||||
},
|
||||
list: {
|
||||
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 = () => {
|
||||
let { notifications, info, onRequestClose } = this.props
|
||||
let { info, onRequestClose } = this.props
|
||||
let notifications: Map<string, Map<string, any>> = this.props.notifications!
|
||||
let parsedDOM: any[] = []
|
||||
if (notifications) {
|
||||
Object.keys(notifications).forEach((key) => {
|
||||
const { notifierUserId } = notifications![key]
|
||||
notifications.forEach((notification, key) => {
|
||||
const notifierUserId = notification!.get('notifierUserId')
|
||||
const userInfo = info!.get(notifierUserId)
|
||||
parsedDOM.push(
|
||||
<NotifyItem
|
||||
key={key}
|
||||
description={(notifications![key] ? notifications![key].description || '' : '')}
|
||||
fullName={(info![notifierUserId] ? info![notifierUserId].fullName || '' : '')}
|
||||
avatar={(info![notifierUserId] ? info![notifierUserId].avatar || '' : '')}
|
||||
id={key}
|
||||
isSeen={(notifications![key] ? notifications![key].isSeen || false : false)}
|
||||
url={(notifications![key] ? notifications![key].url || '' : '')}
|
||||
description={notification!.get('description', '')}
|
||||
fullName={(userInfo ? userInfo.fullName || '' : '')}
|
||||
avatar={(userInfo ? userInfo.avatar || '' : '')}
|
||||
id={key!}
|
||||
isSeen={notification!.get('isSeen', false)}
|
||||
url={notification!.get('url')}
|
||||
notifierUserId={notifierUserId}
|
||||
closeNotify={onRequestClose}
|
||||
/>
|
||||
@@ -171,10 +188,10 @@ const mapDispatchToProps = (dispatch: any, ownProps: INotifyComponentProps) => {
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: INotifyComponentProps) => {
|
||||
const mapStateToProps = (state: Map<string, any>, ownProps: INotifyComponentProps) => {
|
||||
return {
|
||||
notifications: state.notify.userNotifies,
|
||||
info: state.user.info
|
||||
notifications: state.getIn(['notify', 'userNotifies']),
|
||||
info: state.getIn(['user', 'info'])
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,15 @@ import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { push } from 'react-router-redux'
|
||||
import SvgClose from 'material-ui-icons/Close'
|
||||
import { grey } from 'material-ui/colors'
|
||||
import { withStyles } from 'material-ui/styles'
|
||||
import List, { ListItem, ListItemSecondaryAction, ListItemText } from 'material-ui/List'
|
||||
import SvgClose from '@material-ui/icons/Close'
|
||||
import { grey } from '@material-ui/core/colors'
|
||||
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 UserAvatar from 'components/userAvatar'
|
||||
@@ -15,7 +20,7 @@ import UserAvatar from 'components/userAvatar'
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as notifyActions from 'actions/notifyActions'
|
||||
import * as notifyActions from 'store/actions/notifyActions'
|
||||
|
||||
import { INotifyItemComponentProps } from './INotifyItemComponentProps'
|
||||
import { INotifyItemComponentState } from './INotifyItemComponentState'
|
||||
@@ -167,4 +172,4 @@ const mapStateToProps = (state: any, ownProps: INotifyItemComponentProps) => {
|
||||
}
|
||||
|
||||
// - 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 { Post } from 'core/domain/posts/post'
|
||||
|
||||
import {Map} from 'immutable'
|
||||
export interface IPostComponentProps {
|
||||
|
||||
/**
|
||||
* Post object
|
||||
*
|
||||
* @type {Post}
|
||||
* @memberof IPostComponentProps
|
||||
*/
|
||||
post: Post
|
||||
post: Map<string, any>
|
||||
|
||||
/**
|
||||
* Owner's post avatar
|
||||
@@ -113,7 +110,7 @@ export interface IPostComponentProps {
|
||||
* @type {{[commentId: string]: Comment}}
|
||||
* @memberof ICommentGroupComponentProps
|
||||
*/
|
||||
commentList?: {[commentId: string]: Comment}
|
||||
commentList?: Map<string, Comment>
|
||||
|
||||
/**
|
||||
* Styles
|
||||
|
||||
@@ -8,47 +8,55 @@ import moment from 'moment/moment'
|
||||
import Linkify from 'react-linkify'
|
||||
import copy from 'copy-to-clipboard'
|
||||
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
|
||||
import { Map } from 'immutable'
|
||||
|
||||
// - Material UI
|
||||
import { Card, CardActions, CardHeader, CardMedia, CardContent } from 'material-ui'
|
||||
import Typography from 'material-ui/Typography'
|
||||
import SvgShare from 'material-ui-icons/Share'
|
||||
import SvgLink from 'material-ui-icons/Link'
|
||||
import SvgComment from 'material-ui-icons/Comment'
|
||||
import SvgFavorite from 'material-ui-icons/Favorite'
|
||||
import SvgFavoriteBorder from 'material-ui-icons/FavoriteBorder'
|
||||
import Checkbox from 'material-ui/Checkbox'
|
||||
import Button from 'material-ui/Button'
|
||||
import Divider from 'material-ui/Divider'
|
||||
import { grey } from 'material-ui/colors'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import Menu from 'material-ui/Menu'
|
||||
import { MenuList, MenuItem } from 'material-ui/Menu'
|
||||
import TextField from 'material-ui/TextField'
|
||||
import Dialog from 'material-ui/Dialog'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import MoreVertIcon from 'material-ui-icons/MoreVert'
|
||||
import { ListItemIcon, ListItemText } from 'material-ui/List'
|
||||
import { withStyles } from 'material-ui/styles'
|
||||
import Card from '@material-ui/core/Card'
|
||||
import CardActions from '@material-ui/core/CardActions'
|
||||
import CardHeader from '@material-ui/core/CardHeader'
|
||||
import CardMedia from '@material-ui/core/CardMedia'
|
||||
import CardContent from '@material-ui/core/CardContent'
|
||||
import LinearProgress from '@material-ui/core/LinearProgress'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import SvgShare from '@material-ui/icons/Share'
|
||||
import SvgComment from '@material-ui/icons/Comment'
|
||||
import SvgFavorite from '@material-ui/icons/Favorite'
|
||||
import SvgFavoriteBorder from '@material-ui/icons/FavoriteBorder'
|
||||
import Checkbox from '@material-ui/core/Checkbox'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import Divider from '@material-ui/core/Divider'
|
||||
import { grey } from '@material-ui/core/colors'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import Menu from '@material-ui/core/Menu'
|
||||
import MenuList from '@material-ui/core/MenuList'
|
||||
import MenuItem from '@material-ui/core/MenuItem'
|
||||
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 Grow from 'material-ui/transitions/Grow'
|
||||
import ClickAwayListener from 'material-ui/utils/ClickAwayListener'
|
||||
import Grow from '@material-ui/core/Grow'
|
||||
import ClickAwayListener from '@material-ui/core/ClickAwayListener'
|
||||
import classNames from 'classnames'
|
||||
|
||||
import reactStringReplace from 'react-string-replace'
|
||||
|
||||
// - Import app components
|
||||
import CommentGroup from 'components/commentGroup'
|
||||
import ShareDialog from 'components/shareDialog'
|
||||
import PostWrite from 'components/postWrite'
|
||||
import Img from 'components/img'
|
||||
import IconButtonElement from 'layouts/IconButtonElement'
|
||||
import UserAvatar from 'components/userAvatar'
|
||||
|
||||
// - Import actions
|
||||
import * as voteActions from 'actions/voteActions'
|
||||
import * as postActions from 'actions/postActions'
|
||||
import * as commentActions from 'actions/commentActions'
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as voteActions from 'store/actions/voteActions'
|
||||
import * as postActions from 'store/actions/postActions'
|
||||
import * as commentActions from 'store/actions/commentActions'
|
||||
import * as globalActions from 'store/actions/globalActions'
|
||||
import { IPostComponentProps } from './IPostComponentProps'
|
||||
import { IPostComponentState } from './IPostComponentState'
|
||||
|
||||
@@ -65,7 +73,8 @@ const styles = (theme: any) => ({
|
||||
color: 'rgb(134, 129, 129)',
|
||||
fontSize: 10,
|
||||
fontWeight: 400,
|
||||
padding: 2
|
||||
padding: 2,
|
||||
zIndex: 1
|
||||
},
|
||||
commentCounter: {
|
||||
color: 'rgb(134, 129, 129)',
|
||||
@@ -80,18 +89,6 @@ const styles = (theme: any) => ({
|
||||
pointerEvents: 'none',
|
||||
zIndex: 0
|
||||
},
|
||||
shareLinkPaper: {
|
||||
minHeight: 80,
|
||||
padding: 10,
|
||||
minWidth: 460
|
||||
},
|
||||
clipboard: {
|
||||
fontSize: '18px',
|
||||
textAlign: 'center',
|
||||
marginTop: '10px',
|
||||
color: '#1e882d',
|
||||
fontWeight: 400
|
||||
},
|
||||
postBody: {
|
||||
wordWrap: 'break-word',
|
||||
color: 'rgba(0, 0, 0, 0.87)',
|
||||
@@ -103,6 +100,14 @@ const styles = (theme: any) => ({
|
||||
image: {
|
||||
width: '100%',
|
||||
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
|
||||
*/
|
||||
text: post.body ? post.body : '',
|
||||
text: post.get('body', ''),
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
disableComments: post.disableComments!,
|
||||
disableComments: post.get('disableComments', false),
|
||||
/**
|
||||
* If it's true share will be disabled on post
|
||||
*/
|
||||
disableSharing: post.disableSharing!,
|
||||
disableSharing: post.get('disableSharing', false),
|
||||
/**
|
||||
* Title of share post
|
||||
*/
|
||||
@@ -191,7 +196,8 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
|
||||
*/
|
||||
handleOpenComments = () => {
|
||||
const { getPostComments, commentList, post } = this.props
|
||||
const { id, ownerUserId } = post
|
||||
const id = post.get('id')
|
||||
const ownerUserId = post.get('ownerUserId')
|
||||
if (!commentList) {
|
||||
getPostComments!(ownerUserId!, id!)
|
||||
}
|
||||
@@ -232,14 +238,13 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
|
||||
*/
|
||||
handleDelete = () => {
|
||||
const { post } = this.props
|
||||
this.props.delete!(post.id!)
|
||||
this.props.delete!(post.get('id'))
|
||||
}
|
||||
|
||||
/**
|
||||
* Open post menu
|
||||
*/
|
||||
openPostMenu = (event: any) => {
|
||||
console.log(event.currentTarget)
|
||||
this.setState({
|
||||
postMenuAnchorEl: event.currentTarget,
|
||||
isPostMenuOpen: true
|
||||
@@ -278,7 +283,7 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
|
||||
*/
|
||||
handleOpenShare = () => {
|
||||
const {post} = this.props
|
||||
copy(`${location.origin}/${post.ownerUserId}/posts/${post.id}`)
|
||||
copy(`${location.origin}/${post.get('ownerUserId')}/posts/${post.get('id')}`)
|
||||
this.setState({
|
||||
shareOpen: true
|
||||
})
|
||||
@@ -340,7 +345,7 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
|
||||
render () {
|
||||
const { post, setHomeTitle, goTo, fullName, isPostOwner, commentList, avatar, classes , translate} = this.props
|
||||
const { postMenuAnchorEl, isPostMenuOpen } = this.state
|
||||
const RightIconMenu = () => (
|
||||
const rightIconMenu = (
|
||||
<Manager>
|
||||
<Target>
|
||||
<IconButton
|
||||
@@ -355,21 +360,21 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
|
||||
<Popper
|
||||
placement='bottom-start'
|
||||
eventsEnabled={isPostMenuOpen!}
|
||||
className={classNames({ [classes.popperClose]: !isPostMenuOpen! }, { [classes.popperOpen]: isPostMenuOpen! })}
|
||||
className={classNames({ [classes.popperClose]: !isPostMenuOpen }, { [classes.popperOpen]: isPostMenuOpen })}
|
||||
>
|
||||
<ClickAwayListener onClickAway={this.closePostMenu}>
|
||||
<Grow in={isPostMenuOpen!} >
|
||||
<Grow in={isPostMenuOpen} >
|
||||
<Paper>
|
||||
<MenuList role='menu'>
|
||||
<MenuItem onClick={this.handleOpenPostWrite} > {translate!('post.edit')} </MenuItem>
|
||||
<MenuItem onClick={this.handleDelete} > {translate!('post.delete')} </MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => this.props.toggleDisableComments!(!post.disableComments)} >
|
||||
{post.disableComments ? translate!('post.enableComments') : translate!('post.disableComments')}
|
||||
onClick={() => this.props.toggleDisableComments!(!post.get('disableComments'))} >
|
||||
{post.get('disableComments') ? translate!('post.enableComments') : translate!('post.disableComments')}
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
onClick={() => this.props.toggleSharingComments!(!post.disableSharing)} >
|
||||
{post.disableSharing ? translate!('post.enableSharing') : translate!('post.disableSharing')}
|
||||
onClick={() => this.props.toggleSharingComments!(!post.get('disableSharing'))} >
|
||||
{post.get('disableSharing') ? translate!('post.enableSharing') : translate!('post.disableSharing')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Paper>
|
||||
@@ -379,15 +384,25 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
|
||||
</Manager>
|
||||
)
|
||||
|
||||
const { ownerUserId, ownerDisplayName, creationDate, image, body } = post
|
||||
const {
|
||||
ownerUserId,
|
||||
ownerDisplayName,
|
||||
creationDate,
|
||||
image,
|
||||
body,
|
||||
id,
|
||||
disableComments,
|
||||
commentCounter,
|
||||
disableSharing ,
|
||||
} = post.toJS()
|
||||
// Define variables
|
||||
return (
|
||||
<Card>
|
||||
<Card key={`post-component-${id}`}>
|
||||
<CardHeader
|
||||
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>}
|
||||
action={isPostOwner ? <RightIconMenu /> : ''}
|
||||
action={isPostOwner ? rightIconMenu : ''}
|
||||
>
|
||||
</CardHeader>
|
||||
{image ? (
|
||||
@@ -430,16 +445,16 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
|
||||
<div className={classes.voteCounter}> {this.props.voteCount! > 0 ? this.props.voteCount : ''} </div>
|
||||
</IconButton>
|
||||
</div>
|
||||
{!post.disableComments ?
|
||||
{!disableComments ?
|
||||
(<div style={{ display: 'inherit' }}><IconButton
|
||||
className={classes.iconButton}
|
||||
onClick={this.handleOpenComments}
|
||||
aria-label='Comment'>
|
||||
<SvgComment />
|
||||
<div className={classes.commentCounter}>{post.commentCounter! > 0 ? post.commentCounter : ''} </div>
|
||||
<div className={classes.commentCounter}>{commentCounter! > 0 ? commentCounter : ''} </div>
|
||||
</IconButton>
|
||||
</div>) : ''}
|
||||
{!post.disableSharing ? (<IconButton
|
||||
{!disableSharing ? (<IconButton
|
||||
className={classes.iconButton}
|
||||
onClick={this.handleOpenShare}
|
||||
aria-label='Comment'>
|
||||
@@ -448,33 +463,17 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
|
||||
|
||||
</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*/}
|
||||
<Dialog
|
||||
title='Share On'
|
||||
open={this.state.shareOpen}
|
||||
onClose={this.handleCloseShare}
|
||||
>
|
||||
<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>
|
||||
<ShareDialog
|
||||
onClose={this.handleCloseShare}
|
||||
shareOpen={this.state.shareOpen}
|
||||
onCopyLink={this.handleCopyLink}
|
||||
openCopyLink={this.state.openCopyLink}
|
||||
post={post}
|
||||
|
||||
/>
|
||||
|
||||
<PostWrite
|
||||
open={this.state.openPostWrite}
|
||||
onRequestClose={this.handleClosePostWrite}
|
||||
@@ -497,20 +496,18 @@ export class PostComponent extends Component<IPostComponentProps, IPostComponent
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IPostComponentProps) => {
|
||||
const { post } = ownProps
|
||||
return {
|
||||
vote: () => dispatch(voteActions.dbAddVote(post.id!, post.ownerUserId!)),
|
||||
unvote: () => dispatch(voteActions.dbDeleteVote(post.id!, post.ownerUserId!)),
|
||||
vote: () => dispatch(voteActions.dbAddVote(post.get('id'), post.get('ownerUserId'))),
|
||||
unvote: () => dispatch(voteActions.dbDeleteVote(post.get('id'), post.get('ownerUserId'))),
|
||||
delete: (id: string) => dispatch(postActions.dbDeletePost(id)),
|
||||
toggleDisableComments: (status: boolean) => {
|
||||
post.disableComments = status
|
||||
dispatch(postActions.dbUpdatePost(post, (x: any) => x))
|
||||
dispatch(postActions.dbUpdatePost(post.set('disableComments', status), (x: any) => x))
|
||||
},
|
||||
toggleSharingComments: (status: boolean) => {
|
||||
post.disableSharing = status
|
||||
dispatch(postActions.dbUpdatePost(post, (x: any) => x))
|
||||
dispatch(postActions.dbUpdatePost(post.set('disableSharing', status), (x: any) => x))
|
||||
},
|
||||
goTo: (url: string) => dispatch(push(url)),
|
||||
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
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: IPostComponentProps) => {
|
||||
const { post, vote, authorize, comment } = state
|
||||
const { uid } = authorize
|
||||
let currentUserVote = ownProps.post.votes ? ownProps.post.votes[uid] : false
|
||||
const postModel = post.userPosts[ownProps.post.ownerUserId!][ownProps.post.id!]
|
||||
const postOwner = (post.userPosts[uid] ? Object.keys(post.userPosts[uid]).filter((key) => { return ownProps.post.id === key }).length : 0)
|
||||
const commentList: { [commentId: string]: Comment } = comment.postComments[ownProps.post.id!]
|
||||
const mapStateToProps = (state: Map<string, any>, ownProps: IPostComponentProps) => {
|
||||
|
||||
const uid = state.getIn(['authorize', 'uid'])
|
||||
let currentUserVote = ownProps.post.getIn(['votes', uid], false)
|
||||
const voteCount = state.getIn(['post', 'userPosts', ownProps.post.get('ownerUserId'), ownProps.post.get('id'), 'score'], 0)
|
||||
const commentList: { [commentId: string]: Comment } = state.getIn(['comment', 'postComments', ownProps.post.get('id')])
|
||||
const user = state.getIn(['user', 'info', ownProps.post.get('ownerUserId')])
|
||||
return {
|
||||
translate: getTranslate(state.locale),
|
||||
translate: getTranslate(state.get('locale')),
|
||||
commentList,
|
||||
avatar: state.user.info && state.user.info[ownProps.post.ownerUserId!] ? state.user.info[ownProps.post.ownerUserId!].avatar || '' : '',
|
||||
fullName: state.user.info && state.user.info[ownProps.post.ownerUserId!] ? state.user.info[ownProps.post.ownerUserId!].fullName || '' : '',
|
||||
voteCount: postModel.score,
|
||||
avatar: user ? user.avatar : '',
|
||||
fullName: user ? user.fullName : '',
|
||||
voteCount,
|
||||
currentUserVote,
|
||||
isPostOwner: postOwner > 0
|
||||
isPostOwner: uid === ownProps.post.get('ownerUserId')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Post } from 'core/domain/posts'
|
||||
|
||||
import {Map} from 'immutable'
|
||||
export interface IPostWriteComponentProps {
|
||||
|
||||
/**
|
||||
@@ -55,7 +55,7 @@ export interface IPostWriteComponentProps {
|
||||
/**
|
||||
* Post model
|
||||
*/
|
||||
postModel?: Post
|
||||
postModel?: Map<string, any>
|
||||
|
||||
/**
|
||||
* Save a post
|
||||
@@ -69,7 +69,7 @@ export interface IPostWriteComponentProps {
|
||||
*
|
||||
* @memberof IPostWriteComponentProps
|
||||
*/
|
||||
update?: (post: Post, callback: Function) => any
|
||||
update?: (post: Map<string, any>, callback: Function) => any
|
||||
|
||||
/**
|
||||
* Styles
|
||||
|
||||
@@ -3,36 +3,36 @@ import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
|
||||
import { Map } from 'immutable'
|
||||
|
||||
import { Card, CardActions, CardHeader, CardMedia, CardContent } from 'material-ui'
|
||||
import List, {
|
||||
ListItem,
|
||||
ListItemAvatar,
|
||||
ListItemIcon,
|
||||
ListItemSecondaryAction,
|
||||
ListItemText
|
||||
} from 'material-ui/List'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import Dialog, {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle
|
||||
} from 'material-ui/Dialog'
|
||||
import Button from 'material-ui/Button'
|
||||
import RaisedButton from 'material-ui/Button'
|
||||
import { grey } from 'material-ui/colors'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import TextField from 'material-ui/TextField'
|
||||
import Tooltip from 'material-ui/Tooltip'
|
||||
import { MenuList, MenuItem } from 'material-ui/Menu'
|
||||
import SvgRemoveImage from 'material-ui-icons/RemoveCircle'
|
||||
import SvgCamera from 'material-ui-icons/PhotoCamera'
|
||||
import MoreVertIcon from 'material-ui-icons/MoreVert'
|
||||
import { withStyles } from 'material-ui/styles'
|
||||
import { Card, CardActions, CardHeader, CardMedia, CardContent } from '@material-ui/core'
|
||||
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 Paper from '@material-ui/core/Paper'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
import DialogContentText from '@material-ui/core/DialogContentText'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import RaisedButton from '@material-ui/core/Button'
|
||||
import { grey } from '@material-ui/core/colors'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import Tooltip from '@material-ui/core/Tooltip'
|
||||
import MenuList from '@material-ui/core/MenuList'
|
||||
import MenuItem from '@material-ui/core/MenuItem'
|
||||
import SvgRemoveImage from '@material-ui/icons/RemoveCircle'
|
||||
import SvgCamera from '@material-ui/icons/PhotoCamera'
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { Manager, Target, Popper } from 'react-popper'
|
||||
import Grow from 'material-ui/transitions/Grow'
|
||||
import ClickAwayListener from 'material-ui/utils/ClickAwayListener'
|
||||
import Grow from '@material-ui/core/Grow'
|
||||
import ClickAwayListener from '@material-ui/core/ClickAwayListener'
|
||||
import classNames from 'classnames'
|
||||
|
||||
// - Import app components
|
||||
@@ -44,13 +44,22 @@ import UserAvatarComponent from 'components/userAvatar'
|
||||
import * as PostAPI from 'api/PostAPI'
|
||||
|
||||
// - Import actions
|
||||
import * as imageGalleryActions from 'actions/imageGalleryActions'
|
||||
import * as postActions from 'actions/postActions'
|
||||
import * as imageGalleryActions from 'store/actions/imageGalleryActions'
|
||||
import * as postActions from 'store/actions/postActions'
|
||||
import { IPostWriteComponentProps } from './IPostWriteComponentProps'
|
||||
import { IPostWriteComponentState } from './IPostWriteComponentState'
|
||||
import { Post } from 'core/domain/posts'
|
||||
import Grid from '@material-ui/core/Grid/Grid'
|
||||
|
||||
const styles = (theme: any) => ({
|
||||
fullPageXs: {
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
margin: 0,
|
||||
overflowY: 'auto'
|
||||
}
|
||||
},
|
||||
backdrop: {
|
||||
top: 0,
|
||||
left: 0,
|
||||
@@ -62,7 +71,7 @@ const styles = (theme: any) => ({
|
||||
backgroundColor: 'rgba(251, 249, 249, 0.5)',
|
||||
WebkitTapHighlightColor: 'transparent'
|
||||
},
|
||||
root: {
|
||||
content: {
|
||||
padding: 0,
|
||||
paddingTop: 0
|
||||
},
|
||||
@@ -88,7 +97,7 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
|
||||
* Component constructor
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: IPostWriteComponentProps) {
|
||||
constructor(props: IPostWriteComponentProps) {
|
||||
|
||||
super(props)
|
||||
|
||||
@@ -99,15 +108,15 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@@ -123,11 +132,11 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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)
|
||||
}
|
||||
} else { // In edit status we pass post to update functions
|
||||
postModel!.body = postText
|
||||
postModel!.tags = tags
|
||||
postModel!.image = image
|
||||
postModel!.imageFullPath = imageFullPath
|
||||
postModel!.disableComments = disableComments
|
||||
postModel!.disableSharing = disableSharing
|
||||
const updatedPost = postModel!.set('body', postText)
|
||||
.set('tags', tags)
|
||||
.set('image', image)
|
||||
.set('imageFullPath', imageFullPath)
|
||||
.set('disableComments', disableComments)
|
||||
.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) {
|
||||
const { postModel } = this.props
|
||||
this.setState({
|
||||
/**
|
||||
* Post text
|
||||
*/
|
||||
postText: this.props.edit && postModel ? (postModel.body ? postModel.body! : '') : '',
|
||||
/**
|
||||
* The URL image of the post
|
||||
*/
|
||||
image: this.props.edit && postModel ? (postModel.image ? postModel.image! : '') : '',
|
||||
/**
|
||||
* The path identifier of image on the server
|
||||
*/
|
||||
imageFullPath: this.props.edit && postModel ? (postModel.imageFullPath ? postModel.imageFullPath! : '') : '',
|
||||
/**
|
||||
* If it's true gallery will be open
|
||||
*/
|
||||
galleryOpen: false,
|
||||
/**
|
||||
* If it's true post button will be disabled
|
||||
*/
|
||||
disabledPost: true,
|
||||
/**
|
||||
* If it's true comment will be disabled on post
|
||||
*/
|
||||
disableComments: this.props.edit && postModel ? postModel.disableComments! : false,
|
||||
/**
|
||||
* If it's true share will be disabled on post
|
||||
*/
|
||||
disableSharing: this.props.edit && postModel ? postModel.disableSharing! : false
|
||||
/**
|
||||
* Post text
|
||||
*/
|
||||
postText: this.props.edit && postModel ? postModel.get('body', '') : '',
|
||||
/**
|
||||
* The URL image of the post
|
||||
*/
|
||||
image: this.props.edit && postModel ? postModel.get('image', '') : '',
|
||||
/**
|
||||
* The path identifier of image on the server
|
||||
*/
|
||||
imageFullPath: this.props.edit && postModel ? postModel.get('imageFullPath', '') : '',
|
||||
/**
|
||||
* If it's true gallery will be open
|
||||
*/
|
||||
galleryOpen: false,
|
||||
/**
|
||||
* Whether menu is open
|
||||
*/
|
||||
menuOpen: false,
|
||||
/**
|
||||
* If it's true post button will be disabled
|
||||
*/
|
||||
disabledPost: true,
|
||||
/**
|
||||
* If it's true comment will be disabled on post
|
||||
*/
|
||||
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
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
render() {
|
||||
|
||||
const { classes, translate } = this.props
|
||||
const { menuOpen } = this.state
|
||||
@@ -423,28 +436,28 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
|
||||
*/
|
||||
const loadImage = (this.state.image && this.state.image !== '')
|
||||
? (
|
||||
<div>
|
||||
<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' }}>
|
||||
<div style={{ display: 'flex', position: 'relative' }}>
|
||||
<span onClick={this.handleRemoveImage} style={{
|
||||
position: 'absolute', width: '28px', backgroundColor: 'rgba(255, 255, 255, 0.22)',
|
||||
height: '28px', right: 12, top: 4, cursor: 'pointer', borderRadius: '50%',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center'
|
||||
}}>
|
||||
<SvgRemoveImage style={{ color: 'rgba(0, 0, 0, 0.53)' }} />
|
||||
</span>
|
||||
<div>
|
||||
<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' }}>
|
||||
<div style={{ display: 'flex', position: 'relative' }}>
|
||||
<span onClick={this.handleRemoveImage} style={{
|
||||
position: 'absolute', width: '28px', backgroundColor: 'rgba(255, 255, 255, 0.22)',
|
||||
height: '28px', right: 12, top: 4, cursor: 'pointer', borderRadius: '50%',
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center'
|
||||
}}>
|
||||
<SvgRemoveImage style={{ color: 'rgba(0, 0, 0, 0.53)' }} />
|
||||
</span>
|
||||
|
||||
<div style={{ display: 'inline-block', width: '100%', marginRight: '8px', transition: 'transform .25s' }}>
|
||||
<li style={{ width: '100%', margin: 0, verticalAlign: 'bottom', position: 'static' }}>
|
||||
<Img fileName={this.state.image} style={{ width: '100%', height: 'auto' }} />
|
||||
</li>
|
||||
<div style={{ display: 'inline-block', width: '100%', marginRight: '8px', transition: 'transform .25s' }}>
|
||||
<li style={{ width: '100%', margin: 0, verticalAlign: 'bottom', position: 'static' }}>
|
||||
<Img fileName={this.state.image} style={{ width: '100%', height: 'auto' }} />
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</ul>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : ''
|
||||
|
||||
const styles = {
|
||||
@@ -459,14 +472,15 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
|
||||
<div style={this.props.style}>
|
||||
{this.props.children}
|
||||
<Dialog
|
||||
BackdropProps={{className: classes.backdrop} as any}
|
||||
BackdropProps={{ className: classes.backdrop } as any}
|
||||
PaperProps={{className: classes.fullPageXs}}
|
||||
key={this.props.id || 0}
|
||||
open={this.props.open}
|
||||
onClose={this.props.onRequestClose}
|
||||
>
|
||||
<DialogContent
|
||||
className={classes.root}
|
||||
style={{paddingTop: 0}}
|
||||
className={classes.content}
|
||||
style={{ paddingTop: 0 }}
|
||||
|
||||
>
|
||||
|
||||
@@ -478,10 +492,11 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
|
||||
>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Grid item xs={12}>
|
||||
<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' }}>
|
||||
<TextField
|
||||
autoFocus
|
||||
autoFocus
|
||||
value={this.state.postText}
|
||||
onChange={this.handleOnChange}
|
||||
placeholder={translate!('post.textareaPlaceholder')}
|
||||
@@ -504,6 +519,7 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Grid>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</DialogContent>
|
||||
@@ -515,8 +531,8 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
|
||||
onClick={this.props.onRequestClose}
|
||||
style={{ color: grey[800] }}
|
||||
>
|
||||
{translate!('post.cancelButton')}
|
||||
</Button>
|
||||
{translate!('post.cancelButton')}
|
||||
</Button>
|
||||
<Button
|
||||
color='primary'
|
||||
disableFocusRipple={true}
|
||||
@@ -529,6 +545,7 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
<Dialog
|
||||
PaperProps={{className: classes.fullPageXs}}
|
||||
open={this.state.galleryOpen}
|
||||
onClose={this.handleCloseGallery}
|
||||
|
||||
@@ -545,11 +562,11 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
|
||||
style={{ color: grey[800] }}
|
||||
>
|
||||
{translate!('post.cancelButton')}
|
||||
</Button>
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
</div>
|
||||
</div >
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -563,7 +580,7 @@ export class PostWriteComponent extends Component<IPostWriteComponentProps, IPos
|
||||
const mapDispatchToProps = (dispatch: any, ownProps: IPostWriteComponentProps) => {
|
||||
return {
|
||||
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
|
||||
* @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 {
|
||||
translate: getTranslate(state.locale),
|
||||
postImageState: state.imageGallery.status,
|
||||
ownerAvatar: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].avatar : '',
|
||||
ownerDisplayName: state.user.info && state.user.info[state.authorize.uid] ? state.user.info[state.authorize.uid].fullName : ''
|
||||
translate: getTranslate(state.get('locale')),
|
||||
postImageState: state.getIn(['imageGallery', 'status']),
|
||||
ownerAvatar: user.avatar || '',
|
||||
ownerDisplayName: user.fullName || ''
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,6 +63,11 @@ export interface IProfileHeaderComponentProps {
|
||||
*/
|
||||
userId: string
|
||||
|
||||
/**
|
||||
* Whether edit profile is open
|
||||
*/
|
||||
editProfileOpen?: boolean
|
||||
|
||||
/**
|
||||
* Translate to locale string
|
||||
*/
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
// - Import react components
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import config from 'src/config'
|
||||
import {Map} from 'immutable'
|
||||
|
||||
// - Material UI
|
||||
import { grey } from 'material-ui/colors'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import MoreVertIcon from 'material-ui-icons/MoreVert'
|
||||
import { MenuList, MenuItem } from 'material-ui/Menu'
|
||||
import Button from 'material-ui/Button'
|
||||
import RaisedButton from 'material-ui/Button'
|
||||
import { grey } from '@material-ui/core/colors'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert'
|
||||
import MenuList from '@material-ui/core/MenuList'
|
||||
import MenuItem from '@material-ui/core/MenuItem'
|
||||
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 { Parallax, Background } from 'react-parallax'
|
||||
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
|
||||
@@ -23,8 +25,8 @@ import UserAvatar from 'components/userAvatar'
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as userActions from 'actions/userActions'
|
||||
import * as globalActions from 'store/actions/globalActions'
|
||||
import * as userActions from 'store/actions/userActions'
|
||||
import { IProfileHeaderComponentProps } from './IProfileHeaderComponentProps'
|
||||
import { IProfileHeaderComponentState } from './IProfileHeaderComponentState'
|
||||
|
||||
@@ -33,39 +35,6 @@ import { IProfileHeaderComponentState } from './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
|
||||
* @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
|
||||
*/
|
||||
render () {
|
||||
const {translate, isAuthedUser} = this.props
|
||||
const {translate, isAuthedUser, editProfileOpen} = this.props
|
||||
const styles = {
|
||||
avatar: {
|
||||
border: '2px solid rgb(255, 255, 255)'
|
||||
@@ -186,7 +155,7 @@ export class ProfileHeaderComponent extends Component<IProfileHeaderComponentPro
|
||||
/>
|
||||
<div className='left'>
|
||||
{/* 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='fullName'>
|
||||
{this.props.fullName}
|
||||
@@ -207,7 +176,7 @@ export class ProfileHeaderComponent extends Component<IProfileHeaderComponentPro
|
||||
</div>) : ''}
|
||||
</div>
|
||||
</div>
|
||||
{isAuthedUser ? (<EditProfile
|
||||
{isAuthedUser && editProfileOpen ? (<EditProfile
|
||||
avatar={this.props.avatar}
|
||||
banner={this.props.banner}
|
||||
fullName={this.props.fullName}
|
||||
@@ -235,10 +204,11 @@ const mapDispatchToProps = (dispatch: any, ownProps: IProfileHeaderComponentProp
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: IProfileHeaderComponentProps) => {
|
||||
const mapStateToProps = (state: Map<string, any>, ownProps: IProfileHeaderComponentProps) => {
|
||||
|
||||
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 { ServerRequestModel } from 'models/server/serverRequestModel'
|
||||
import { Profile } from 'core/domain/users'
|
||||
import { ServerRequestStatusType } from 'store/actions/serverRequestStatusType'
|
||||
|
||||
export interface ISendFeedbackComponentProps {
|
||||
/**
|
||||
@@ -21,13 +22,18 @@ export interface ISendFeedbackComponentProps {
|
||||
/**
|
||||
* The server request of send feedback
|
||||
*/
|
||||
sendFeedbackRequest?: ServerRequestModel
|
||||
sendFeedbackRequestType?: ServerRequestStatusType
|
||||
|
||||
/**
|
||||
* Current user profile
|
||||
*/
|
||||
currentUser?: Profile
|
||||
|
||||
/**
|
||||
* Styles
|
||||
*/
|
||||
classes?: any
|
||||
|
||||
/**
|
||||
* Translate to locale string
|
||||
*/
|
||||
|
||||
@@ -2,22 +2,27 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from 'react-redux'
|
||||
import Paper from 'material-ui/Paper'
|
||||
import TextField from 'material-ui/TextField'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
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/Progress'
|
||||
import Tooltip from 'material-ui/Tooltip'
|
||||
import classNames from 'classnames'
|
||||
import {Map} from 'immutable'
|
||||
|
||||
// - Material UI
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import IconButton from '@material-ui/core/IconButton'
|
||||
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 { withStyles } from '@material-ui/core/styles'
|
||||
|
||||
// - Import app components
|
||||
|
||||
// - Import API
|
||||
|
||||
// - Import actions
|
||||
import { globalActions } from 'actions'
|
||||
import { globalActions } from 'store/actions'
|
||||
|
||||
import { Feed } from 'core/domain/common'
|
||||
import { ISendFeedbackComponentProps } from './ISendFeedbackComponentProps'
|
||||
@@ -28,7 +33,18 @@ import { Profile } from 'core/domain/users'
|
||||
import StringAPI from 'api/StringAPI'
|
||||
import { ServerRequestType } from 'constants/serverRequestType'
|
||||
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
|
||||
@@ -74,7 +90,7 @@ export class SendFeedbackComponent extends Component<ISendFeedbackComponentProps
|
||||
}
|
||||
|
||||
mainForm = () => {
|
||||
const { sendFeedbackStatus, hideFeedback, sendFeed, sendFeedbackRequest, translate } = this.props
|
||||
const { sendFeedbackStatus, hideFeedback, sendFeed, sendFeedbackRequestType, translate } = this.props
|
||||
const { feedText } = this.state
|
||||
return (
|
||||
<div className='main-box'>
|
||||
@@ -134,9 +150,7 @@ export class SendFeedbackComponent extends Component<ISendFeedbackComponentProps
|
||||
color='secondary'
|
||||
size={50}
|
||||
variant='determinate'
|
||||
value={25}
|
||||
min={0}
|
||||
max={50}
|
||||
value={(25 - 0) / (50 - 0) * 100}
|
||||
/>
|
||||
</div>
|
||||
</div>)
|
||||
@@ -153,11 +167,11 @@ export class SendFeedbackComponent extends Component<ISendFeedbackComponentProps
|
||||
}
|
||||
|
||||
getFeedbackForm = () => {
|
||||
const { sendFeedbackStatus, hideFeedback, sendFeed, sendFeedbackRequest } = this.props
|
||||
const { sendFeedbackStatus, hideFeedback, sendFeed, sendFeedbackRequestType } = this.props
|
||||
const { feedText } = this.state
|
||||
|
||||
if (sendFeedbackRequest) {
|
||||
switch (sendFeedbackRequest.status) {
|
||||
if (sendFeedbackRequestType) {
|
||||
switch (sendFeedbackRequestType) {
|
||||
case ServerRequestStatusType.Sent:
|
||||
return this.loadingForm()
|
||||
|
||||
@@ -180,11 +194,11 @@ export class SendFeedbackComponent extends Component<ISendFeedbackComponentProps
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
const { sendFeedbackStatus, hideFeedback, sendFeed, sendFeedbackRequest } = this.props
|
||||
const { sendFeedbackStatus, hideFeedback, sendFeed, sendFeedbackRequestType, classes } = this.props
|
||||
const { feedText } = this.state
|
||||
|
||||
return (
|
||||
<div className='sendFeedback__content animate__up'>
|
||||
<div className={classNames('sendFeedback__content', 'animate__up',classes.fullPageXs)}>
|
||||
<Paper className='paper' >
|
||||
<div className='close'>
|
||||
<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
|
||||
* @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 } = server
|
||||
const { uid } = authorize
|
||||
const currentUser: User = user.info && user.info[uid] ? { ...user.info[uid], userId: uid } : {}
|
||||
const { sendFeedbackStatus } = global
|
||||
const sendFeedbackRequest: ServerRequestModel = request ? request[StringAPI.createServerRequestId(ServerRequestType.CommonSendFeedback, uid)] : null
|
||||
const request = state.getIn(['server', 'request'])
|
||||
const uid = state.getIn(['authorize', 'uid'])
|
||||
const requestId = StringAPI.createServerRequestId(ServerRequestType.CommonSendFeedback, uid)
|
||||
const currentUser: User = { ...state.getIn(['user', 'info', uid], {}), userId: uid }
|
||||
const sendFeedbackStatus = state.getIn(['global', 'sendFeedbackStatus'])
|
||||
const sendFeedbackRequestType = state.getIn(['server', 'request', requestId])
|
||||
|
||||
return {
|
||||
translate: getTranslate(state.locale),
|
||||
translate: getTranslate(state.get('locale')),
|
||||
sendFeedbackStatus,
|
||||
sendFeedbackRequest,
|
||||
sendFeedbackRequestType: sendFeedbackRequestType ? sendFeedbackRequestType.status : ServerRequestStatusType.NoAction,
|
||||
currentUser
|
||||
}
|
||||
}
|
||||
|
||||
// - 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 actions
|
||||
import * as authorizeActions from 'actions/authorizeActions'
|
||||
import * as globalActions from 'actions/globalActions'
|
||||
import * as authorizeActions from 'store/actions/authorizeActions'
|
||||
import * as globalActions from 'store/actions/globalActions'
|
||||
import { ISidebarComponentProps } from './ISidebarComponentProps'
|
||||
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 PropTypes from 'prop-types'
|
||||
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 API
|
||||
|
||||
// - Import actions
|
||||
import * as imageGalleryActions from 'actions/imageGalleryActions'
|
||||
import * as imageGalleryActions from 'store/actions/imageGalleryActions'
|
||||
|
||||
import { IUserAvatarComponentProps } from './IUserAvatarComponentProps'
|
||||
import { IUserAvatarComponentState } from './IUserAvatarComponentState'
|
||||
@@ -95,8 +96,8 @@ const mapDispatchToProps = (dispatch: Function, ownProps: IUserAvatarComponentPr
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: IUserAvatarComponentProps) => {
|
||||
return {
|
||||
avatarURL: state.imageGallery.imageURLList,
|
||||
imageRequests: state.imageGallery.imageRequests
|
||||
avatarURL: state.getIn(['imageGallery', 'imageURLList']),
|
||||
imageRequests: state.getIn(['imageGallery', 'imageRequests'])
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,30 @@
|
||||
import { User } from 'core/domain/users'
|
||||
import { Circle } from 'core/domain/circles/circle'
|
||||
import { UserTie } from 'core/domain/circles'
|
||||
import { ServerRequestStatusType } from 'actions/serverRequestStatusType'
|
||||
import { ServerRequestStatusType } from 'store/actions/serverRequestStatusType'
|
||||
import { ServerRequestModel } from 'models/server/serverRequestModel'
|
||||
|
||||
import {Map, List} from 'immutable'
|
||||
export interface IUserBoxComponentProps {
|
||||
|
||||
/**
|
||||
* User identifier
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IUserBoxComponentProps
|
||||
*/
|
||||
userId: string
|
||||
|
||||
/**
|
||||
* User
|
||||
*
|
||||
* @type {User}
|
||||
* @memberof IUserBoxComponentProps
|
||||
*/
|
||||
user: UserTie
|
||||
|
||||
/**
|
||||
* Circles
|
||||
*
|
||||
* @type {{[circleId: string]: Circle}}
|
||||
* @memberof IUserBoxComponentProps
|
||||
*/
|
||||
circles?: {[circleId: string]: Circle}
|
||||
circles?: Map<string, Map<string, any>>
|
||||
|
||||
/**
|
||||
* List of circles' id
|
||||
*
|
||||
* @type {string[]}
|
||||
* @memberof IUserBoxComponentProps
|
||||
*/
|
||||
userBelongCircles?: string[]
|
||||
userBelongCircles?: List<string>
|
||||
|
||||
/**
|
||||
* Whether current user followed this user
|
||||
@@ -45,54 +33,38 @@ export interface IUserBoxComponentProps {
|
||||
|
||||
/**
|
||||
* The number of circles
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof IUserBoxComponentProps
|
||||
*/
|
||||
belongCirclesCount?: number
|
||||
|
||||
/**
|
||||
* The first circle
|
||||
*
|
||||
* @type {User}
|
||||
* @memberof IUserBoxComponentProps
|
||||
*/
|
||||
firstBelongCircle?: Circle
|
||||
firstBelongCircle?: Map<string, any>
|
||||
|
||||
/**
|
||||
* Avatar address
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IUserBoxComponentProps
|
||||
*/
|
||||
avatar?: string
|
||||
|
||||
/**
|
||||
* User full name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IUserBoxComponentProps
|
||||
*/
|
||||
fullName?: string
|
||||
|
||||
/**
|
||||
* The `Following` circle identifier of current user
|
||||
*/
|
||||
followingCircleId?: string
|
||||
followingCircle?: Map<string, any>
|
||||
|
||||
/**
|
||||
* Create a circle
|
||||
*
|
||||
* @memberof IUserBoxComponentProps
|
||||
*/
|
||||
createCircle?: (name: string) => any
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -107,12 +79,12 @@ export interface IUserBoxComponentProps {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
removeSelectedCircles?: (userId: string, circleList: string[]) => any
|
||||
removeSelectedCircles?: (userId: string, circleList: List<string>) => any
|
||||
|
||||
/**
|
||||
* Open select circle box
|
||||
@@ -126,8 +98,6 @@ export interface IUserBoxComponentProps {
|
||||
|
||||
/**
|
||||
* Redirect page to [url]
|
||||
*
|
||||
* @memberof IUserBoxComponentProps
|
||||
*/
|
||||
goTo?: (url: string) => any
|
||||
|
||||
@@ -149,7 +119,7 @@ export interface IUserBoxComponentProps {
|
||||
/**
|
||||
* Keep selected circles for refere user
|
||||
*/
|
||||
selectedCircles?: string[]
|
||||
selectedCircles?: List<string>
|
||||
|
||||
/**
|
||||
* 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 { push } from 'react-router-redux'
|
||||
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
|
||||
import classNames from 'classnames'
|
||||
import {Map, List as ImuList} from 'immutable'
|
||||
|
||||
// - Material UI
|
||||
import Paper from 'material-ui/Paper'
|
||||
import Button from 'material-ui/Button'
|
||||
import RaisedButton from 'material-ui/Button'
|
||||
import Menu, { MenuItem } from 'material-ui/Menu'
|
||||
import Checkbox from 'material-ui/Checkbox'
|
||||
import TextField from 'material-ui/TextField'
|
||||
import Tooltip from 'material-ui/Tooltip'
|
||||
import { withStyles } from 'material-ui/styles'
|
||||
import List, { ListItem, ListItemSecondaryAction, ListItemText } from 'material-ui/List'
|
||||
import Divider from 'material-ui/Divider'
|
||||
import Dialog, {
|
||||
DialogActions,
|
||||
DialogContent,
|
||||
DialogContentText,
|
||||
DialogTitle,
|
||||
withMobileDialog
|
||||
} from 'material-ui/Dialog'
|
||||
import SvgAdd from 'material-ui-icons/Add'
|
||||
import IconButton from 'material-ui/IconButton'
|
||||
import { grey } from 'material-ui/colors'
|
||||
import Paper from '@material-ui/core/Paper'
|
||||
import Button from '@material-ui/core/Button'
|
||||
import RaisedButton from '@material-ui/core/Button'
|
||||
import MenuList from '@material-ui/core/MenuList'
|
||||
import MenuItem from '@material-ui/core/MenuItem'
|
||||
import Checkbox from '@material-ui/core/Checkbox'
|
||||
import TextField from '@material-ui/core/TextField'
|
||||
import Tooltip from '@material-ui/core/Tooltip'
|
||||
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 Divider from '@material-ui/core/Divider'
|
||||
import Dialog from '@material-ui/core/Dialog'
|
||||
import DialogActions from '@material-ui/core/DialogActions'
|
||||
import DialogContent from '@material-ui/core/DialogContent'
|
||||
import DialogTitle from '@material-ui/core/DialogTitle'
|
||||
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 UserAvatar from 'components/userAvatar'
|
||||
@@ -35,14 +40,14 @@ import UserAvatar from 'components/userAvatar'
|
||||
import StringAPI from 'api/StringAPI'
|
||||
|
||||
// - Import actions
|
||||
import * as circleActions from 'actions/circleActions'
|
||||
import * as circleActions from 'store/actions/circleActions'
|
||||
|
||||
import { IUserBoxComponentProps } from './IUserBoxComponentProps'
|
||||
import { IUserBoxComponentState } from './IUserBoxComponentState'
|
||||
import { User } from 'core/domain/users'
|
||||
import { UserTie, Circle } from 'core/domain/circles'
|
||||
import { ServerRequestType } from 'constants/serverRequestType'
|
||||
import { ServerRequestStatusType } from 'actions/serverRequestStatusType'
|
||||
import { ServerRequestStatusType } from 'store/actions/serverRequestStatusType'
|
||||
import { ServerRequestModel } from 'models/server'
|
||||
|
||||
const styles = (theme: any) => ({
|
||||
@@ -51,6 +56,14 @@ const styles = (theme: any) => ({
|
||||
maxWidth: 360,
|
||||
backgroundColor: theme.palette.background.paper
|
||||
},
|
||||
paper: {
|
||||
height: 254,
|
||||
width: 243,
|
||||
margin: 10,
|
||||
textAlign: 'center',
|
||||
minWidth: 230,
|
||||
maxWidth: '257px'
|
||||
},
|
||||
dialogContent: {
|
||||
paddingTop: '5px',
|
||||
padding: '0px 5px 5px 5px'
|
||||
@@ -60,6 +73,14 @@ const styles = (theme: any) => ({
|
||||
},
|
||||
space: {
|
||||
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 = {
|
||||
paper: {
|
||||
height: 254,
|
||||
width: 243,
|
||||
margin: 10,
|
||||
textAlign: 'center',
|
||||
maxWidth: '257px'
|
||||
},
|
||||
followButton: {
|
||||
position: 'absolute',
|
||||
bottom: '30px',
|
||||
@@ -102,7 +116,6 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
|
||||
borderRadius: '4px'
|
||||
}
|
||||
}
|
||||
selectedCircles: string[]
|
||||
|
||||
/**
|
||||
* Component constructor
|
||||
@@ -130,7 +143,6 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
|
||||
*/
|
||||
disabledDoneCircles: true
|
||||
}
|
||||
this.selectedCircles = userBelongCircles!.slice()
|
||||
// Binding functions to `this`
|
||||
this.handleChangeName = this.handleChangeName.bind(this)
|
||||
this.onCreateCircle = this.onCreateCircle.bind(this)
|
||||
@@ -143,11 +155,10 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
|
||||
* Handle follow user
|
||||
*/
|
||||
handleDoneAddCircle = () => {
|
||||
const { userId, user, addUserToCircle, selectedCircles, deleteFollowingUser } = this.props
|
||||
const { avatar, fullName } = user
|
||||
const { userId, user, addUserToCircle, selectedCircles, deleteFollowingUser, avatar, fullName } = this.props
|
||||
const { disabledDoneCircles } = this.state
|
||||
if (!disabledDoneCircles) {
|
||||
if (selectedCircles!.length > 0) {
|
||||
if (selectedCircles!.count() > 0) {
|
||||
addUserToCircle!(selectedCircles!, { avatar, userId, fullName })
|
||||
} else {
|
||||
deleteFollowingUser!(userId)
|
||||
@@ -161,14 +172,13 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
|
||||
onFollowUser = (event: any) => {
|
||||
// This prevents ghost click
|
||||
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) {
|
||||
return
|
||||
}
|
||||
const { avatar, fullName } = user
|
||||
if (!isFollowed) {
|
||||
followUser!(followingCircleId!, { avatar, userId, fullName })
|
||||
followUser!(followingCircle!.get('id'), { avatar, userId, fullName })
|
||||
} else {
|
||||
this.onRequestOpenAddCircle()
|
||||
}
|
||||
@@ -224,17 +234,13 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
|
||||
|
||||
handleSelectCircle = (event: object, isInputChecked: boolean, circleId: string) => {
|
||||
const { userBelongCircles, circles, setSelectedCircles, selectedCircles, userId } = this.props
|
||||
let newSelectedCircles = selectedCircles!.slice()
|
||||
let newSelectedCircles = selectedCircles!
|
||||
if (isInputChecked) {
|
||||
|
||||
newSelectedCircles = [
|
||||
...selectedCircles!,
|
||||
circleId
|
||||
]
|
||||
newSelectedCircles = selectedCircles!.push(circleId)
|
||||
|
||||
} else {
|
||||
const circleIndex = selectedCircles!.indexOf(circleId)
|
||||
newSelectedCircles.splice(circleIndex, 1)
|
||||
newSelectedCircles = newSelectedCircles.remove(circleIndex)
|
||||
}
|
||||
|
||||
setSelectedCircles!(userId, newSelectedCircles)
|
||||
@@ -248,39 +254,39 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
|
||||
*/
|
||||
circleList = () => {
|
||||
let { circles, userId, userBelongCircles, selectedCircles, classes } = this.props
|
||||
|
||||
const circleDomList: any[] = []
|
||||
if (circles) {
|
||||
|
||||
const parsedDate = Object.keys(circles).map((circleId, index) => {
|
||||
let isBelong = selectedCircles ? selectedCircles!.indexOf(circleId) > -1 : false
|
||||
circles.forEach((circle, circleId) => {
|
||||
let isBelong = selectedCircles ? selectedCircles!.indexOf(circleId!) > -1 : false
|
||||
|
||||
// Create checkbox for selected/unselected circle
|
||||
return (
|
||||
circleDomList.push(
|
||||
<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>
|
||||
<Checkbox
|
||||
onChange={(event: object, isInputChecked: boolean) => this.handleSelectCircle(event, isInputChecked, circleId)}
|
||||
onChange={(event: object, isInputChecked: boolean) => this.handleSelectCircle(event, isInputChecked, circleId!)}
|
||||
checked={isBelong}
|
||||
/>
|
||||
</ListItemSecondaryAction>
|
||||
</ListItem>)
|
||||
})
|
||||
|
||||
return parsedDate
|
||||
return circleDomList
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the the selected circles changed
|
||||
*/
|
||||
selectedCircleChange = (selectedCircles: string[]) => {
|
||||
selectedCircleChange = (selectedCircles: ImuList<string>) => {
|
||||
let isChanged = false
|
||||
const { userBelongCircles, circles } = this.props
|
||||
|
||||
if (selectedCircles.length === userBelongCircles!.length) {
|
||||
for (let circleIndex: number = 0; circleIndex < selectedCircles.length; circleIndex++) {
|
||||
const selectedCircleId = selectedCircles[circleIndex]
|
||||
if (selectedCircles.count() === userBelongCircles!.count()) {
|
||||
for (let circleIndex: number = 0; circleIndex < selectedCircles.count(); circleIndex++) {
|
||||
const selectedCircleId = selectedCircles.get(circleIndex)
|
||||
if (!(userBelongCircles!.indexOf(selectedCircleId) > -1)) {
|
||||
isChanged = true
|
||||
break
|
||||
@@ -298,10 +304,21 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
|
||||
*/
|
||||
render () {
|
||||
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 (
|
||||
<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={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
@@ -321,7 +338,7 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
|
||||
</div>
|
||||
<div onClick={() => this.props.goTo!(`/${this.props.userId}`)} className='people__name' style={{ cursor: 'pointer' }}>
|
||||
<div>
|
||||
{this.props.user.fullName}
|
||||
{this.props.fullName}
|
||||
</div>
|
||||
</div>
|
||||
<div style={this.styles.followButton as any}>
|
||||
@@ -334,11 +351,12 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
|
||||
}
|
||||
>
|
||||
{!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>
|
||||
</div>
|
||||
</div>
|
||||
<Dialog
|
||||
PaperProps={{className: classes.fullPageXs}}
|
||||
key={this.props.userId || 0}
|
||||
open={isSelecteCirclesOpen === true}
|
||||
onClose={this.onRequestCloseAddCircle}
|
||||
@@ -403,7 +421,7 @@ export class UserBoxComponent extends Component<IUserBoxComponentProps, IUserBox
|
||||
const mapDispatchToProps = (dispatch: Function, ownProps: IUserBoxComponentProps) => {
|
||||
return {
|
||||
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)),
|
||||
deleteFollowingUser: (followingId: string) => dispatch(circleActions.dbDeleteFollowingUser(followingId)),
|
||||
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
|
||||
* @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 } = authorize
|
||||
const { request } = server
|
||||
const uid = state.getIn(['authorize', 'uid'])
|
||||
const request = state.getIn(['server', 'request'])
|
||||
|
||||
const circles: { [circleId: string]: Circle } = circle ? (circle.circleList || {}) : {}
|
||||
const userBelongCircles = circle ? (circle.userTies[ownProps.userId] ? circle.userTies[ownProps.userId].circleIdList : []) : []
|
||||
const isFollowed = userBelongCircles.length > 0
|
||||
const followingCircleId = circles ? Object.keys(circles)
|
||||
.filter((circleId) => circles[circleId].isSystem && circles[circleId].name === `Following`)[0] : ''
|
||||
const followRequest: ServerRequestModel = request ? request[StringAPI.createServerRequestId(ServerRequestType.CircleFollowUser, ownProps.userId)] : null
|
||||
const addToCircleRequest: ServerRequestModel = request ? request[StringAPI.createServerRequestId(ServerRequestType.CircleAddToCircle, ownProps.userId)] : null
|
||||
const deleteFollowingUserRequest: ServerRequestModel = request ? request[StringAPI.createServerRequestId(ServerRequestType.CircleDeleteFollowingUser, ownProps.userId)] : null
|
||||
const selectedCircles = circle.selectedCircles ? circle.selectedCircles[ownProps.userId] : []
|
||||
const isSelecteCirclesOpen = circle.openSelecteCircles ? circle.openSelecteCircles[ownProps.userId] : []
|
||||
const circles: Map<string, Map<string, any>> = state.getIn(['circle', 'circleList'], {})
|
||||
const userBelongCircles: ImuList<any> = state.getIn(['circle', 'userTies', ownProps.userId, 'circleIdList'], ImuList())
|
||||
const isFollowed = userBelongCircles.count() > 0
|
||||
const followingCircle = circles
|
||||
.filter((followingCircle) => followingCircle!.get('isSystem', false) && followingCircle!.get('name') === `Following`)
|
||||
.toArray()[0]
|
||||
const followRequestId = StringAPI.createServerRequestId(ServerRequestType.CircleFollowUser, ownProps.userId)
|
||||
const followRequest = state.getIn(['server', 'request', followRequestId])
|
||||
const addToCircleRequestId = StringAPI.createServerRequestId(ServerRequestType.CircleAddToCircle, 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 {
|
||||
translate: getTranslate(state.locale),
|
||||
translate: getTranslate(state.get('locale')),
|
||||
isSelecteCirclesOpen,
|
||||
isFollowed,
|
||||
selectedCircles,
|
||||
circles,
|
||||
followingCircleId,
|
||||
followingCircle,
|
||||
userBelongCircles,
|
||||
followRequest,
|
||||
belongCirclesCount: userBelongCircles.length || 0,
|
||||
firstBelongCircle: userBelongCircles ? (circles ? circles[userBelongCircles[0]] : {}) : {},
|
||||
avatar: state.user.info && state.user.info[ownProps.userId] ? state.user.info[ownProps.userId].avatar || '' : '',
|
||||
fullName: state.user.info && state.user.info[ownProps.userId] ? state.user.info[ownProps.userId].fullName || '' : ''
|
||||
belongCirclesCount: userBelongCircles.count() || 0,
|
||||
firstBelongCircle: userBelongCircles ? circles.get(userBelongCircles.get(0), Map({})) : Map({}),
|
||||
avatar: userBox.avatar || '' ,
|
||||
fullName: userBox.fullName || ''
|
||||
}
|
||||
}
|
||||
|
||||
// - 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 { UserTie } from 'core/domain/circles'
|
||||
import {Map} from 'immutable'
|
||||
|
||||
export interface IUserBoxListComponentProps {
|
||||
|
||||
@@ -9,7 +10,7 @@ export interface IUserBoxListComponentProps {
|
||||
* @type {{[userId: string]: User}}
|
||||
* @memberof IUserBoxListComponentProps
|
||||
*/
|
||||
users: {[userId: string]: UserTie}
|
||||
users: Map<string, UserTie>
|
||||
|
||||
/**
|
||||
* User identifier
|
||||
|
||||
@@ -2,12 +2,14 @@
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import {Map} from 'immutable'
|
||||
|
||||
// - Import app components
|
||||
import UserBox from 'components/userBox'
|
||||
|
||||
import { IUserBoxListComponentProps } from './IUserBoxListComponentProps'
|
||||
import { IUserBoxListComponentState } from './IUserBoxListComponentState'
|
||||
import { UserTie } from 'core/domain/circles/userTie'
|
||||
|
||||
// - Import API
|
||||
|
||||
@@ -42,15 +44,17 @@ export class UserBoxListComponent extends Component<IUserBoxListComponentProps,I
|
||||
}
|
||||
|
||||
userList = () => {
|
||||
let { users, uid } = this.props
|
||||
|
||||
let { uid } = this.props
|
||||
const users = this.props.users
|
||||
const userBoxList: any[] = []
|
||||
if (users) {
|
||||
return Object.keys(users).map((key, index) => {
|
||||
users.forEach((user: UserTie, key: string) => {
|
||||
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
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: IUserBoxListComponentProps) => {
|
||||
const {uid} = state.authorize
|
||||
const uid = state.getIn(['authorize', 'uid'], 0)
|
||||
return {
|
||||
uid
|
||||
}
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
import { Circle } from 'core/domain/circles'
|
||||
|
||||
import {Map} from 'immutable'
|
||||
export interface IYourCirclesComponentProps {
|
||||
|
||||
/**
|
||||
* Circles
|
||||
*
|
||||
* @type {{[circleId: string]: Circle}}
|
||||
* @memberof IYourCirclesComponentProps
|
||||
*/
|
||||
circles?: {[circleId: string]: Circle}
|
||||
circles?: Map<string, Map<string, any>>
|
||||
|
||||
/**
|
||||
* User identifier
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IYourCirclesComponentProps
|
||||
*/
|
||||
uid?: string
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
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 CircleComponent from 'components/circle'
|
||||
@@ -44,8 +46,8 @@ export class YourCirclesComponent extends Component<IYourCirclesComponentProps,I
|
||||
let parsedCircles: any[] = []
|
||||
|
||||
if (circles) {
|
||||
Object.keys(circles).map((key, index) => {
|
||||
parsedCircles.push(<CircleComponent key={key} circle={circles![key]} id={key} uid={uid!} />)
|
||||
circles.map((circle, key) => {
|
||||
parsedCircles.push(<CircleComponent key={key} circle={circle!} id={key!} uid={uid!} />)
|
||||
})
|
||||
}
|
||||
return parsedCircles
|
||||
@@ -95,10 +97,9 @@ const mapDispatchToProps = (dispatch: Function, ownProps: IYourCirclesComponentP
|
||||
* @param {object} ownProps is the props belong to component
|
||||
* @return {object} props of component
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: IYourCirclesComponentProps) => {
|
||||
const {circle, authorize, server} = state
|
||||
const { uid } = state.authorize
|
||||
const circles: { [circleId: string]: Circle } = circle ? (circle.circleList || {}) : {}
|
||||
const mapStateToProps = (state: Map<string, any>, ownProps: IYourCirclesComponentProps) => {
|
||||
const uid = state.getIn(['authorize', 'uid'])
|
||||
const circles: Map<string, Map<string, any>> = state.getIn(['circle', 'circleList'], {})
|
||||
return {
|
||||
uid,
|
||||
circles
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LanguageType } from 'reducers/locale/langugeType'
|
||||
import { LanguageType } from 'store/reducers/locale/langugeType'
|
||||
|
||||
export const environment = {
|
||||
firebase: {
|
||||
@@ -10,6 +10,7 @@ export const environment = {
|
||||
messagingSenderId: '964743099489'
|
||||
},
|
||||
settings: {
|
||||
enabledOAuthLogin: true,
|
||||
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',
|
||||
defaultLanguage: LanguageType.English
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LanguageType } from 'reducers/locale/langugeType'
|
||||
import { LanguageType } from 'store/reducers/locale/langugeType'
|
||||
|
||||
export const environment = {
|
||||
firebase: {
|
||||
@@ -10,6 +10,7 @@ export const environment = {
|
||||
messagingSenderId: '964743099489'
|
||||
},
|
||||
settings: {
|
||||
enabledOAuthLogin: true,
|
||||
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',
|
||||
defaultLanguage: LanguageType.English
|
||||
|
||||
@@ -7,4 +7,5 @@ export enum CommentActionType {
|
||||
UPDATE_COMMENT = 'UPDATE_COMMENT',
|
||||
CLOSE_COMMENT_EDITOR = 'CLOSE_COMMENT_EDITOR',
|
||||
OPEN_COMMENT_EDITOR = 'OPEN_COMMENT_EDITOR',
|
||||
DB_FETCH_COMMENTS = 'DB_FETCH_COMMENTS',
|
||||
}
|
||||
@@ -17,6 +17,7 @@ export enum GlobalActionType {
|
||||
TEMP = 'TEMP',
|
||||
CLEAR_ALL_GLOBAL = 'CLEAR_ALL_GLOBAL',
|
||||
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_POST = 'ADD_POST',
|
||||
UPDATE_POST = 'UPDATE_POST',
|
||||
UPDATE_POST_COMMENTS = 'UPDATE_POST_COMMENTS',
|
||||
UPDATE_POST_VOTES = 'UPDATE_POST_VOTES',
|
||||
DELETE_POST = 'DELETE_POST',
|
||||
ADD_LIST_POST = 'ADD_LIST_POST',
|
||||
CLEAR_ALL_DATA_POST = 'CLEAR_ALL_DATA_POST'
|
||||
|
||||
@@ -3,17 +3,38 @@ 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 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 config from 'src/config'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { getTranslate, getActiveLanguage } from 'react-localize-redux'
|
||||
|
||||
// - Import actions
|
||||
import * as authorizeActions from 'actions/authorizeActions'
|
||||
import * as authorizeActions from 'src/store/actions/authorizeActions'
|
||||
import { IEmailVerificationComponentProps } from './IEmailVerificationComponentProps'
|
||||
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
|
||||
@@ -22,7 +43,7 @@ import { IEmailVerificationComponentState } from './IEmailVerificationComponentS
|
||||
* @class EmailVerificationComponent
|
||||
* @extends {Component}
|
||||
*/
|
||||
export class EmailVerificationComponent extends Component<IEmailVerificationComponentProps,IEmailVerificationComponentState> {
|
||||
export class EmailVerificationComponent extends Component<IEmailVerificationComponentProps, IEmailVerificationComponentState> {
|
||||
|
||||
styles = {
|
||||
message: {
|
||||
@@ -32,7 +53,18 @@ export class EmailVerificationComponent extends Component<IEmailVerificationComp
|
||||
marginTop: 60
|
||||
},
|
||||
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
|
||||
* @param {object} props is an object properties of component
|
||||
*/
|
||||
constructor (props: IEmailVerificationComponentProps) {
|
||||
constructor(props: IEmailVerificationComponentProps) {
|
||||
super(props)
|
||||
|
||||
// Binding function to `this`
|
||||
@@ -52,47 +84,26 @@ export class EmailVerificationComponent extends Component<IEmailVerificationComp
|
||||
* Reneder component DOM
|
||||
* @return {react element} return the DOM which rendered by component
|
||||
*/
|
||||
render () {
|
||||
const {translate} = this.props
|
||||
const paperStyle = {
|
||||
minHeight: 370,
|
||||
width: 450,
|
||||
textAlign: 'center',
|
||||
display: 'block',
|
||||
margin: 'auto'
|
||||
}
|
||||
render() {
|
||||
const { translate, classes } = this.props
|
||||
return (
|
||||
<div>
|
||||
<Grid container spacing={24}>
|
||||
<Grid item xs={12} className={classes.contain}>
|
||||
|
||||
<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>
|
||||
<h1 className='g__app-name'>{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'
|
||||
}}>
|
||||
<div className='animate-bottom'>
|
||||
<Paper className={classes.paper} 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!('emailVerification.title')}</h2>
|
||||
</div>
|
||||
<p style={this.styles.message as any}>
|
||||
{translate!('emailVerification.description')}
|
||||
<h2 className='zoomOutLCorner animated g__paper-title'>{translate!('emailVerification.title')}</h2>
|
||||
</div>
|
||||
<p style={this.styles.message as any}>
|
||||
{translate!('emailVerification.description')}
|
||||
</p>
|
||||
<div style={this.styles.buttons}>
|
||||
<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>
|
||||
</Paper>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Paper>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -132,9 +144,9 @@ const mapDispatchToProps = (dispatch: Function, ownProps: IEmailVerificationComp
|
||||
*/
|
||||
const mapStateToProps = (state: any, ownProps: IEmailVerificationComponentProps) => {
|
||||
return {
|
||||
translate: getTranslate(state.locale)
|
||||
translate: getTranslate(state.get('locale'))
|
||||
}
|
||||
}
|
||||
|
||||
// - 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
|
||||
|
||||
/**
|
||||
* Styles
|
||||
*/
|
||||
classes?: any
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
@@ -113,4 +113,14 @@ export interface IHomeComponentProps {
|
||||
*/
|
||||
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 {
|
||||
|
||||
/**
|
||||
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 {
|
||||
/**
|
||||
* Close gloal message
|
||||
@@ -4,18 +4,20 @@ import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { Route, Switch, NavLink, withRouter, Redirect } from 'react-router-dom'
|
||||
import { push } from 'react-router-redux'
|
||||
import Snackbar from 'material-ui/Snackbar'
|
||||
import { LinearProgress } from 'material-ui/Progress'
|
||||
import Snackbar from '@material-ui/core/Snackbar'
|
||||
import LinearProgress from '@material-ui/core/LinearProgress'
|
||||
import {Helmet} from 'react-helmet'
|
||||
import {Map} from 'immutable'
|
||||
|
||||
// - Import components
|
||||
|
||||
import MasterLoading from 'components/masterLoading'
|
||||
import SendFeedback from 'components/sendFeedback'
|
||||
import MasterRouter from 'routes/MasterRouter'
|
||||
import MasterLoading from 'src/components/masterLoading'
|
||||
import SendFeedback from 'src/components/sendFeedback'
|
||||
import MasterRouter from 'src/routes/MasterRouter'
|
||||
import { IMasterComponentProps } from './IMasterComponentProps'
|
||||
import { IMasterComponentState } from './IMasterComponentState'
|
||||
import { ServiceProvide, IServiceProvider } from 'core/factories'
|
||||
import { IAuthorizeService } from 'core/services/authorize'
|
||||
import { ServiceProvide, IServiceProvider } from 'src/core/factories'
|
||||
import { IAuthorizeService } from 'src/core/services/authorize'
|
||||
|
||||
// - Import actions
|
||||
import {
|
||||
@@ -28,7 +30,7 @@ import {
|
||||
globalActions,
|
||||
circleActions,
|
||||
notifyActions
|
||||
} from 'actions'
|
||||
} from 'src/store/actions'
|
||||
|
||||
/* ------------------------------------ */
|
||||
|
||||
@@ -53,7 +55,6 @@ export class MasterComponent extends Component<IMasterComponentProps, IMasterCom
|
||||
}
|
||||
|
||||
// Binding functions to `this`
|
||||
this.handleLoading = this.handleLoading.bind(this)
|
||||
this.handleMessage = this.handleMessage.bind(this)
|
||||
|
||||
}
|
||||
@@ -63,14 +64,6 @@ export class MasterComponent extends Component<IMasterComponentProps, IMasterCom
|
||||
this.props.closeMessage()
|
||||
}
|
||||
|
||||
// Handle loading
|
||||
handleLoading = (status: boolean) => {
|
||||
this.setState({
|
||||
loading: status,
|
||||
authed: false
|
||||
})
|
||||
}
|
||||
|
||||
componentDidCatch (error: any, info: any) {
|
||||
console.log('===========Catched by React componentDidCatch==============')
|
||||
console.log(error, info)
|
||||
@@ -130,6 +123,11 @@ export class MasterComponent extends Component<IMasterComponentProps, IMasterCom
|
||||
|
||||
return (
|
||||
<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 /> : ''}
|
||||
<div className='master__progress' style={{ display: (progress.visible ? 'block' : 'none') }}>
|
||||
<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='title'>Loading ... </div>
|
||||
</div>
|
||||
<MasterLoading activeLoading={global.showMasterLoading} handleLoading={this.handleLoading} />
|
||||
{progress.visible ? <MasterLoading /> : ''}
|
||||
<MasterRouter enabled={!loading} data={{uid}} />
|
||||
<Snackbar
|
||||
open={this.props.global.messageOpen}
|
||||
@@ -196,15 +194,16 @@ const mapDispatchToProps = (dispatch: any, ownProps: IMasterComponentProps) => {
|
||||
* Map state to props
|
||||
* @param {object} state
|
||||
*/
|
||||
const mapStateToProps = (state: any) => {
|
||||
const { authorize, global, user, post, comment, imageGallery, vote, notify, circle } = state
|
||||
const { sendFeedbackStatus } = global
|
||||
const mapStateToProps = (state: Map<string, any>) => {
|
||||
const authorize = Map(state.get('authorize', {})).toJS()
|
||||
const global = Map(state.get('global', {})).toJS()
|
||||
const { sendFeedbackStatus, progress } = global
|
||||
return {
|
||||
sendFeedbackStatus,
|
||||
progress,
|
||||
guest: authorize.guest,
|
||||
uid: authorize.uid,
|
||||
authed: authorize.authed,
|
||||
progress: global.progress,
|
||||
global: global
|
||||
}
|
||||
|
||||