Merge pull request #1 from Qolzam/next

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

View File

@@ -1,23 +1,37 @@
# Change Log
## [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)

View File

@@ -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**
[![Install React Social Network](https://img.youtube.com/vi/zrqDE82Eny8/0.jpg)](https://www.youtube.com/watch?v=zrqDE82Eny8)
#### [Firestore Social Backend](https://github.com/Qolzam/firestore-social-backend)
#### With [Firestore](https://github.com/Qolzam/firestore-social-backend)
* Configure firebase:
* 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

View File

@@ -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.

View File

@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 241 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

BIN
docs/app/layer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 KiB

28
docs/layers.md Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -1,6 +1,35 @@
{
"hosting": {
"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

File diff suppressed because it is too large Load Diff

View File

@@ -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"
}
}

View File

@@ -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>

View File

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

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

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

View File

@@ -4,11 +4,14 @@ import * as moment from 'moment/moment'
* @param title log title
* @param 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
}

View File

@@ -1,14 +1,3 @@
// - Interface declaration
interface FileReaderEventTarget extends EventTarget {
result: string
}
interface FileReaderEvent extends Event {
target: FileReaderEventTarget
getMessage (): string
}
// - Get file Extension
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')

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
*/

View File

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

View File

@@ -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'])
}
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)

View File

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

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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}

View File

@@ -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
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

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

View File

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

View File

@@ -2,20 +2,26 @@
import React, { Component } from 'react'
import { 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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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'))
}
}

View File

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

View File

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

View File

@@ -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>
)

View File

@@ -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

View File

@@ -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'])
}
}

View File

@@ -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 )

View File

@@ -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

View File

@@ -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')
}
}

View File

@@ -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

View File

@@ -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 || ''
}
}

View File

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

View File

@@ -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'])
}
}

View File

@@ -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
*/

View File

@@ -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)

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,8 +10,8 @@ import keycode from 'keycode'
// - Import API
// - Import 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'

View File

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

View File

@@ -2,14 +2,15 @@
import React, { Component } from 'react'
import 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'])
}
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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',
}

View File

@@ -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'
}

View File

@@ -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'

View File

@@ -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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
}

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