diff --git a/src/AC/index.js b/src/AC/index.js index dda2dd6..ec3bc53 100644 --- a/src/AC/index.js +++ b/src/AC/index.js @@ -1,4 +1,4 @@ -import {INCREMENT, DELETE_ARTICLE, CHANGE_DATE_RANGE, CHANGE_SELECTION} from '../constants' +import {INCREMENT, DELETE_ARTICLE, CHANGE_DATE_RANGE, CHANGE_SELECTION, ADD_COMMENT, OPEN_ARTICLE} from '../constants' export function increment() { return { @@ -6,6 +6,13 @@ export function increment() { } } +export function openArticle(id) { + return { + type: OPEN_ARTICLE, + payload: { id } + } +} + export function deleteArticle(id) { return { type: DELETE_ARTICLE, @@ -26,3 +33,10 @@ export function changeSelection(selected) { payload: { selected } } } + +export function addComment(comment) { + return { + type: ADD_COMMENT, + payload: { comment } + } +} \ No newline at end of file diff --git a/src/components/ArticleList.js b/src/components/ArticleList.js index bc1c261..b2af4c5 100644 --- a/src/components/ArticleList.js +++ b/src/components/ArticleList.js @@ -10,9 +10,9 @@ class ArticleList extends Accordion { console.log('---', 'rerendering article list') const {articles} = this.props if (!articles.length) return

No Articles

- const articleElements = articles.map((article) =>
  • + const articleElements = Object.values(articles).map((article) =>
  • ) @@ -36,6 +36,7 @@ ArticleList.propTypes = { export default connect(state => { console.log('---', 'connect updated') return { + openItemId: state.openArticleId, articles: filtratedArticlesSelector(state) } })(ArticleList) \ No newline at end of file diff --git a/src/components/CommentForm/index.js b/src/components/CommentForm/index.js index 3685a10..daf3dda 100644 --- a/src/components/CommentForm/index.js +++ b/src/components/CommentForm/index.js @@ -1,4 +1,7 @@ import React, { Component } from 'react' +import {connect} from 'react-redux' +import {addComment} from '../../AC' + import './style.css' class CommentForm extends Component { @@ -26,6 +29,13 @@ class CommentForm extends Component { handleSubmit = ev => { ev.preventDefault() + + const {addComment} = this.props + addComment({ + user: this.state.user, + text: this.state.text + }) + this.setState({ user: '', text: '' @@ -58,4 +68,4 @@ const limits = { } } -export default CommentForm \ No newline at end of file +export default connect(null, { addComment })(CommentForm) \ No newline at end of file diff --git a/src/components/Filters/Select.js b/src/components/Filters/Select.js index 1a91857..a9252b6 100644 --- a/src/components/Filters/Select.js +++ b/src/components/Filters/Select.js @@ -8,14 +8,14 @@ import 'react-select/dist/react-select.css' class SelectFilter extends Component { static propTypes = { - articles: PropTypes.array.isRequired + articles: PropTypes.object.isRequired }; handleChange = selected => this.props.changeSelection(selected.map(option => option.value)) render() { const { articles, selected } = this.props - const options = articles.map(article => ({ + const options = Object.values(articles).map(article => ({ label: article.title, value: article.id })) diff --git a/src/components/common/Accordion.js b/src/components/common/Accordion.js index 1d63067..bd3a160 100644 --- a/src/components/common/Accordion.js +++ b/src/components/common/Accordion.js @@ -1,4 +1,6 @@ import React, { Component } from 'react' +import {openArticle} from '../../AC' +import store from '../../store' class Accordion extends Component { state = { @@ -6,9 +8,7 @@ class Accordion extends Component { } toggleOpenItem = openItemId => ev => { - this.setState({ - openItemId: openItemId === this.state.openItemId ? null : openItemId - }) + store.dispatch(openArticle(openItemId === this.props.openItemId ? null : openItemId)) } toggleOpenItemMemoized = (openItemId) => { diff --git a/src/constants/index.js b/src/constants/index.js index a2f0a80..27f2e6f 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -1,6 +1,9 @@ export const INCREMENT = 'INCREMENT' +export const OPEN_ARTICLE = 'OPEN_ARTICLE' export const DELETE_ARTICLE = 'DELETE_ARTICLE' export const CHANGE_SELECTION = 'CHANGE_SELECTION' -export const CHANGE_DATE_RANGE = 'CHANGE_DATE_RANGE' \ No newline at end of file +export const CHANGE_DATE_RANGE = 'CHANGE_DATE_RANGE' + +export const ADD_COMMENT = 'ADD_COMMENT' \ No newline at end of file diff --git a/src/middlewares/idGenerator.js b/src/middlewares/idGenerator.js new file mode 100644 index 0000000..ac39b75 --- /dev/null +++ b/src/middlewares/idGenerator.js @@ -0,0 +1,25 @@ +import {ADD_COMMENT} from '../constants' + +export default store => next => action => { + function genId(len) { + function getRandomChar() { + let number = Math.round(Math.random() * 15); + return number < 10 ? String.fromCharCode(97 + number) : String.fromCharCode(39 + number) + } + + let result = getRandomChar(); + for (let i = 0; i < len - 1; i++) { + result += getRandomChar(); + } + return result; + } + + switch (action.type) { + case ADD_COMMENT: + action.payload.id = genId(10) + action.payload.articleId = store.getState().openArticleId + break; + } + + next(action) +} \ No newline at end of file diff --git a/src/reducer/articles.js b/src/reducer/articles.js index 480a522..bf3312a 100644 --- a/src/reducer/articles.js +++ b/src/reducer/articles.js @@ -1,12 +1,30 @@ -import { DELETE_ARTICLE } from '../constants' +import { DELETE_ARTICLE, ADD_COMMENT, OPEN_ARTICLE } from '../constants' import {normalizedArticles as defaultArticles} from '../fixtures' -export default (articlesState = defaultArticles, action) => { +const articlesMap = defaultArticles.reduce((acc, article) => { + article.comments = article.comments != null ? article.comments : [] + return { + ...acc, + [article.id]: article + } +}, {}) + +export default (articlesState = articlesMap, action) => { const { type, payload } = action switch (type) { case DELETE_ARTICLE: - return articlesState.filter(article => article.id !== payload.id) + articlesState = {...articlesState} + delete articlesState[payload.id] + return articlesState + case ADD_COMMENT: + let article = {...articlesState[payload.articleId]} + article.comments = [...article.comments] + article.comments.push(payload.id) + return { + ...articlesState, + [payload.articleId]: article + } } return articlesState diff --git a/src/reducer/comments.js b/src/reducer/comments.js index 30bd10c..b2d7308 100644 --- a/src/reducer/comments.js +++ b/src/reducer/comments.js @@ -1,4 +1,4 @@ -import { } from '../constants' +import { ADD_COMMENT } from '../constants' import {normalizedComments as defaultComments} from '../fixtures' const commentsMap = defaultComments.reduce((acc, comment) => ({ @@ -10,7 +10,15 @@ export default (state = commentsMap, action) => { const { type } = action switch (type) { - + case ADD_COMMENT: + state = { + ...state, + [action.payload.id]: { + ...action.payload.comment, + id: action.payload.id + } + } + return state; } return state diff --git a/src/reducer/index.js b/src/reducer/index.js index 25a0b0b..634f091 100644 --- a/src/reducer/index.js +++ b/src/reducer/index.js @@ -3,8 +3,10 @@ import counterReducer from './counter' import articles from './articles' import comments from './comments' import filters from './filters' +import openArticle from './openArticle'; export default combineReducers({ counter: counterReducer, - articles, comments, filters + articles, comments, filters, + openArticleId: openArticle }) \ No newline at end of file diff --git a/src/reducer/openArticle.js b/src/reducer/openArticle.js new file mode 100644 index 0000000..fdb2415 --- /dev/null +++ b/src/reducer/openArticle.js @@ -0,0 +1,5 @@ +import {OPEN_ARTICLE} from '../constants' + +export default (openArticleId = null, action) => { + return action.type === OPEN_ARTICLE ? action.payload.id : openArticleId +} \ No newline at end of file diff --git a/src/selectors/index.js b/src/selectors/index.js index 4e3172b..df03af0 100644 --- a/src/selectors/index.js +++ b/src/selectors/index.js @@ -14,7 +14,7 @@ export const filtratedArticlesSelector = createSelector(articlesSelector, filter console.log('---', 'computing filters') const {selected, dateRange: {from, to}} = filters - return articles.filter(article => { + return Object.values(articles).filter(article => { const published = Date.parse(article.date) return (!selected.length || selected.includes(article.id)) && (!from || !to || (published > from && published < to)) diff --git a/src/store/index.js b/src/store/index.js index f83d367..733e3ba 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,6 +1,7 @@ import {createStore, applyMiddleware, compose} from 'redux' import rootReducer from '../reducer' import logger from '../middlewares/logger' +import idGenerator from '../middlewares/idGenerator' const composeEnhancers = typeof window === 'object' && @@ -10,7 +11,7 @@ const composeEnhancers = }) : compose const enhancer = composeEnhancers( - applyMiddleware(logger) + applyMiddleware(logger, idGenerator) ) const store = createStore(rootReducer, enhancer)