at the end of the day, it was inevitable
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
import { applyMiddleware, compose, createStore } from 'redux';
|
||||
import thunk from 'redux-thunk';
|
||||
import { routerMiddleware } from 'react-router-redux';
|
||||
import { history } from '../main';
|
||||
|
||||
export const hasBrowserExt = window.__REDUX_DEVTOOLS_EXTENSION__;
|
||||
|
||||
export default function configureStore(initialState, rootReducer) {
|
||||
const middleware = applyMiddleware(thunk, routerMiddleware(history));
|
||||
|
||||
let createStoreWithMiddleware;
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
if (hasBrowserExt) {
|
||||
// show browser devtools if available
|
||||
const storeEnhancers =
|
||||
(typeof window === 'object' &&
|
||||
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ||
|
||||
compose;
|
||||
|
||||
createStoreWithMiddleware = storeEnhancers(middleware);
|
||||
} else {
|
||||
createStoreWithMiddleware = compose(
|
||||
middleware,
|
||||
require('./utils/DevTools').default.instrument()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
createStoreWithMiddleware = compose(middleware);
|
||||
}
|
||||
|
||||
const store = createStoreWithMiddleware(createStore)(
|
||||
rootReducer,
|
||||
initialState
|
||||
);
|
||||
|
||||
return store;
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
import { createAction, handleActions } from 'redux-actions'
|
||||
import { fromJS } from 'immutable'
|
||||
import {thunkAction} from '../../utils/common'
|
||||
|
||||
export class ReduxModule {
|
||||
|
||||
constructor () {
|
||||
this._namespace = this.getNamespace()
|
||||
this._actions = {}
|
||||
this._reducers = {}
|
||||
}
|
||||
|
||||
init () {
|
||||
this._initialState = fromJS(this.getInitialState())
|
||||
const actions = this.defineActions()
|
||||
const reducers = this._addNamespaceToReducers(this.defineReducers())
|
||||
Object.assign(this._actions, actions)
|
||||
Object.assign(this._reducers, reducers)
|
||||
this.reducers = handleActions(this._reducers, this._initialState)
|
||||
this.actions = this._actions
|
||||
}
|
||||
|
||||
getNamespace () {
|
||||
//implement in subclasses
|
||||
}
|
||||
|
||||
getInitialState () {
|
||||
//implement in subclasses
|
||||
}
|
||||
|
||||
defineActions () {
|
||||
//implement in subclasses
|
||||
//it should return hash of function
|
||||
/**
|
||||
* return {
|
||||
* action1,
|
||||
* action2
|
||||
* }
|
||||
*/
|
||||
}
|
||||
|
||||
defineReducers () {
|
||||
//implement in subclasses
|
||||
/*
|
||||
return {
|
||||
[actionName]: this.****Reducer()
|
||||
}
|
||||
or use this.addReducer()
|
||||
*/
|
||||
}
|
||||
|
||||
_addNamespaceToReducers (obj) {
|
||||
const result = {}
|
||||
for (let actionName in obj) {
|
||||
result[this.ns(actionName)] = obj[actionName]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/** utils **/
|
||||
ns (actionName) {
|
||||
return `${this._namespace} ${actionName}`
|
||||
}
|
||||
|
||||
evalPath (path) {
|
||||
return path
|
||||
//TODO: something better
|
||||
/*return path.map(
|
||||
(item) => (typeof item === 'function') ? item(state) : item
|
||||
);*/
|
||||
}
|
||||
|
||||
/** action creators **/
|
||||
createAction (actionName, actionFn) {
|
||||
return createAction(this.ns(actionName), actionFn)
|
||||
}
|
||||
|
||||
thunkAction (actionName, actionMethod, emitPending) {
|
||||
return thunkAction(this.ns(actionName), actionMethod, emitPending)
|
||||
}
|
||||
|
||||
thunkPendingReducer (field) {
|
||||
return (state, {payload: {isPending}}) => state.set(field, isPending)
|
||||
}
|
||||
|
||||
/** reducer creators **/
|
||||
setReducer (field) {
|
||||
return (state, {payload: value}) => state.set(field, value)
|
||||
}
|
||||
|
||||
setInReducer (path) {
|
||||
return (state, {payload: value}) => state.setIn(this.evalPath(path), value)
|
||||
}
|
||||
|
||||
setFieldReducer () {
|
||||
return (state, {payload: {field, value}}) => state.set(field, value)
|
||||
}
|
||||
|
||||
resetReducer (field, defaultValue) {
|
||||
return (state) => state.set(field, defaultValue)
|
||||
}
|
||||
|
||||
mergeReducer () {
|
||||
return (state, {payload: values}) => state.merge(values)
|
||||
}
|
||||
|
||||
mergeInReducer (path) {
|
||||
return (state, {payload: values}) => state.mergeIn(this.evalPath(path), values)
|
||||
}
|
||||
|
||||
toggleReducer (field) {
|
||||
return (state) => state.set(field, !state.get(field))
|
||||
}
|
||||
|
||||
toggleInReducer (path) {
|
||||
return (state) => {
|
||||
const realPath = this.evalPath(path)
|
||||
return state.setIn(realPath, !state.getIn(realPath))
|
||||
}
|
||||
}
|
||||
|
||||
addReducer (actionName, reducerFn) {
|
||||
this._reducers[this.ns(actionName)] = reducerFn
|
||||
}
|
||||
|
||||
//do not prefix with namespace
|
||||
addExternalReducer (actionName, reducerFn) {
|
||||
this._reducers[actionName] = reducerFn
|
||||
}
|
||||
|
||||
/** handler creators
|
||||
* handler = action + reducer
|
||||
**/
|
||||
createHandler (actionName, actionFn, reducerFn) {
|
||||
const action = this.createAction(actionName, actionFn)
|
||||
this.addReducer(actionName, reducerFn)
|
||||
return action
|
||||
}
|
||||
|
||||
set (actionName, field) {
|
||||
return this.createHandler(actionName, {}, this.setReducer(field))
|
||||
}
|
||||
|
||||
setIn (actionName, path) {
|
||||
return this.createHandler(actionName, {}, this.setInReducer(path))
|
||||
}
|
||||
|
||||
setField (actionName) {
|
||||
return this.createHandler(actionName, (field, value) => ({field, value}), this.setFieldReducer())
|
||||
}
|
||||
|
||||
reset (actionName, field, defaultValue) {
|
||||
return this.createHandler(actionName, {}, this.resetReducer(field, defaultValue))
|
||||
}
|
||||
|
||||
merge (actionName) {
|
||||
return this.createHandler(actionName, {}, this.mergeReducer())
|
||||
}
|
||||
|
||||
mergeIn (actionName, path) {
|
||||
return this.createHandler(actionName, {}, this.mergeInReducer(path))
|
||||
}
|
||||
|
||||
toggle (actionName, field) {
|
||||
return this.createHandler(actionName, {}, this.toggleReducer(field))
|
||||
}
|
||||
|
||||
toggleIn (actionName, path) {
|
||||
return this.createHandler(actionName, {}, this.toggleInReducer(path))
|
||||
}
|
||||
|
||||
resetToInitialState (actionName) {
|
||||
return this.createHandler(actionName, {}, () => this._initialState)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default ReduxModule
|
||||
@@ -0,0 +1,49 @@
|
||||
import { fromJS } from 'immutable'
|
||||
import { createAction, handleActions } from 'redux-actions'
|
||||
|
||||
// Action types
|
||||
const ADD_ALERT_CHART = 'ADD_ALERT_CHART'
|
||||
const REMOVE_ALERT_CHART = 'REMOVE_ALERT_CHART'
|
||||
const RESET_ALERT_CHART = 'RESET_ALERT_CHART'
|
||||
|
||||
// Actions
|
||||
const addAlertChart = createAction(ADD_ALERT_CHART, (payload) => payload)
|
||||
const removeAlertChart = createAction(REMOVE_ALERT_CHART, (payload) => payload)
|
||||
const resetAlertChart = createAction(RESET_ALERT_CHART, (payload) => payload)
|
||||
|
||||
export const analyzeActions = {
|
||||
addAlertChart,
|
||||
removeAlertChart,
|
||||
resetAlertChart
|
||||
}
|
||||
|
||||
// Reducer
|
||||
const initialState = fromJS({
|
||||
alertCharts: []
|
||||
})
|
||||
|
||||
export default handleActions(
|
||||
{
|
||||
[ADD_ALERT_CHART]: (state, { payload }) => {
|
||||
const charts = state.getIn(['alertCharts'])
|
||||
if (charts.find((v) => v.name === payload)) {
|
||||
return state
|
||||
}
|
||||
return state.setIn(['alertCharts'], [...charts, payload])
|
||||
},
|
||||
[REMOVE_ALERT_CHART]: (state, { payload }) => {
|
||||
const charts = state
|
||||
.getIn(['alertCharts'])
|
||||
.filter(
|
||||
(item) =>
|
||||
item.name !== payload.name ||
|
||||
(item.id ? item.id !== payload.id : true)
|
||||
)
|
||||
return state.setIn(['alertCharts'], charts)
|
||||
},
|
||||
[RESET_ALERT_CHART]: (state) => {
|
||||
return state.setIn(['alertCharts'], [])
|
||||
}
|
||||
},
|
||||
initialState
|
||||
)
|
||||
@@ -0,0 +1,220 @@
|
||||
/** ----------------CONSTANTS---------------- **/
|
||||
import {createAction, handleActions} from 'redux-actions'
|
||||
import {fromJS, Map} from 'immutable'
|
||||
|
||||
import {getSavedAnalysesApi, deleteSavedAnalysesApi} from '../../../api/analyticApi'
|
||||
import {thunkAction, tokenInject} from '../../utils/common'
|
||||
|
||||
const GET_RECENT_ANALYSIS = 'GET_RECENT_ANALYSIS'
|
||||
|
||||
const CONFIRM_DELETE_ANALYSES = 'CONFIRM_DELETE_ANALYSES'
|
||||
const CANCEL_DELETE_ANALYSES = 'CANCEL_DELETE_ANALYSES'
|
||||
const DELETE_ANALYSES = 'DELETE_ANALYSES'
|
||||
|
||||
const SELECT_TABLE_ROW = 'SELECT_ANALYSES_TABLE_ROW'
|
||||
const SELECT_ALL_ROWS = 'SELECT_ANALYSES_TABLE_ALL_ROWS'
|
||||
const SET_TABLE_PARAMS = 'SET_ANALYSES_TABLE_PARAMS'
|
||||
const RELOAD_TABLE = 'RELOAD_ANALYSES_TABLE'
|
||||
|
||||
const TOGGLE_SIDEBAR = 'TOGGLE_SIDEBAR'
|
||||
|
||||
/** -------------ACTIONS------------------------ **/
|
||||
|
||||
const getRecentAnalysis = thunkAction(GET_RECENT_ANALYSIS, ({token, fulfilled}) => {
|
||||
return getSavedAnalysesApi(token, {sort: 'id'}).then(fulfilled)
|
||||
})
|
||||
|
||||
const confirmDeleteAnalyses = createAction(CONFIRM_DELETE_ANALYSES, (idsArray) => idsArray)
|
||||
const cancelDeleteAnalyses = createAction(CANCEL_DELETE_ANALYSES)
|
||||
const deleteSavedAnalysesPending = () => {
|
||||
return {type: DELETE_ANALYSES + '_PENDING'}
|
||||
}
|
||||
|
||||
const deleteSavedAnalysesFulfilled = (payload) => {
|
||||
return {type: DELETE_ANALYSES + '_FULFILLED', payload}
|
||||
}
|
||||
|
||||
const deleteSavedAnalyses = () => {
|
||||
return tokenInject((dispatch, getState, token) => {
|
||||
dispatch(deleteSavedAnalysesPending())
|
||||
const tableState = getState().getIn(['appState', 'analyzeTab', 'tableState']).toJS()
|
||||
deleteSavedAnalysesApi(
|
||||
token, tableState.idsToDelete
|
||||
).then(() => {
|
||||
return getSavedAnalysesApi(token, {
|
||||
page: tableState.currentPage,
|
||||
limit: tableState.limitByPage,
|
||||
sort: tableState.sortByField,
|
||||
direction: tableState.sortDirection
|
||||
})
|
||||
}).then((data) => {
|
||||
dispatch(deleteSavedAnalysesFulfilled(data))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
Change table page or sort state
|
||||
params = {currentPage, limitByPage, sortField, sortDirection}
|
||||
*/
|
||||
const setAnalysesTableParams = createAction(SET_TABLE_PARAMS, (params) => params)
|
||||
const selectAnalysesTableRow = createAction(SELECT_TABLE_ROW, (itemId) => itemId)
|
||||
const selectAnalysesTableAllRows = createAction(SELECT_ALL_ROWS)
|
||||
|
||||
const reloadAnalysesTablePending = () => {
|
||||
return {type: RELOAD_TABLE + '_PENDING'}
|
||||
}
|
||||
|
||||
const reloadAnalysesTableFulfilled = (payload) => {
|
||||
return {type: RELOAD_TABLE + '_FULFILLED', payload}
|
||||
}
|
||||
|
||||
const reloadAnalysesTable = (params) => {
|
||||
return tokenInject((dispatch, getState, token) => {
|
||||
dispatch(reloadAnalysesTablePending())
|
||||
dispatch(setAnalysesTableParams(params))
|
||||
const state = getState().getIn(['appState', 'analyzeTab', 'tableState']).toJS()
|
||||
getSavedAnalysesApi(token, {
|
||||
page: state.currentPage,
|
||||
limit: state.limitByPage,
|
||||
sort: state.sortByField,
|
||||
direction: state.sortDirection
|
||||
}).then((data) => {
|
||||
dispatch(reloadAnalysesTableFulfilled(data))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
getRecentAnalysis,
|
||||
confirmDeleteAnalyses,
|
||||
cancelDeleteAnalyses,
|
||||
deleteSavedAnalyses,
|
||||
setAnalysesTableParams,
|
||||
selectAnalysesTableAllRows,
|
||||
selectAnalysesTableRow,
|
||||
reloadAnalysesTable
|
||||
}
|
||||
|
||||
/** -----------STATE--------- **/
|
||||
|
||||
export const initialState = fromJS({
|
||||
//for welcome tab
|
||||
recentAnalysis: [],
|
||||
isRecentAnalysisPending: false,
|
||||
//for saved tab
|
||||
isSidebarVisible: true,
|
||||
sidebarState: {
|
||||
isRecentlyViewedPending: true,
|
||||
recentlyViewed: []
|
||||
},
|
||||
tableState: {
|
||||
currentPage: 1,
|
||||
limitByPage: 10,
|
||||
sortByField: 'id',
|
||||
sortDirection: 'asc',
|
||||
data: [],
|
||||
count: 0,
|
||||
totalCount: 0,
|
||||
isLoading: true,
|
||||
isDeletePopupVisible: false,
|
||||
idsToDelete: [],
|
||||
selectedIds: {}, //map of ids of items that selected in table
|
||||
isAllSelected: false
|
||||
}
|
||||
})
|
||||
|
||||
/** -----------HANDLERS--------------- **/
|
||||
|
||||
export default handleActions({
|
||||
[`${GET_RECENT_ANALYSIS}_PENDING`]: (state) => {
|
||||
return state.merge({
|
||||
recentAnalysis: [],
|
||||
isRecentAnalysisPending: true
|
||||
})
|
||||
},
|
||||
|
||||
[`${GET_RECENT_ANALYSIS}_FULFILLED`]: (state, {payload}) => {
|
||||
return state.merge({
|
||||
recentAnalysis: payload.data,
|
||||
isRecentAnalysisPending: false
|
||||
})
|
||||
},
|
||||
|
||||
[CONFIRM_DELETE_ANALYSES]: (state, {payload: idsArray}) => {
|
||||
return state.mergeIn(['tableState'], {
|
||||
isDeletePopupVisible: true,
|
||||
idsToDelete: idsArray
|
||||
})
|
||||
},
|
||||
|
||||
[CANCEL_DELETE_ANALYSES]: (state) => {
|
||||
return state.mergeIn(['tableState'], {
|
||||
isDeletePopupVisible: false,
|
||||
idsToDelete: []
|
||||
})
|
||||
},
|
||||
|
||||
[`${DELETE_ANALYSES}_PENDING`]: (state) => {
|
||||
return state.mergeIn(['tableState'], {
|
||||
isDeletePopupVisible: false,
|
||||
isLoading: true
|
||||
})
|
||||
},
|
||||
|
||||
[`${DELETE_ANALYSES}_FULFILLED`]: (state, {payload}) => {
|
||||
return state.mergeIn(['tableState'], {
|
||||
data: payload.data,
|
||||
count: payload.count,
|
||||
totalCount: payload.totalCount,
|
||||
isLoading: false
|
||||
})
|
||||
},
|
||||
|
||||
[SET_TABLE_PARAMS]: (state, {payload: tableState}) => {
|
||||
return state.mergeIn(['tableState'], tableState)
|
||||
},
|
||||
|
||||
[SELECT_TABLE_ROW]: (state, {payload: {itemId, select}}) => {
|
||||
const path = ['tableState', 'selectedIds', itemId.toString()]
|
||||
return select ? state.setIn(path, 1) : state.deleteIn(path)
|
||||
},
|
||||
|
||||
[SELECT_ALL_ROWS]: (state) => {
|
||||
const isAllSelected = state.getIn(['tableState', 'isAllSelected'])
|
||||
|
||||
if (isAllSelected) { //then deselect all
|
||||
return state.mergeIn(['tableState'], {
|
||||
isAllSelected: false,
|
||||
selectedIds: {}
|
||||
})
|
||||
} else { //select all currently loaded data
|
||||
let selectedIds = {}
|
||||
const data = state.getIn(['tableState', 'data']).toJS()
|
||||
data.forEach((item) => { selectedIds[item.id.toString()] = 1 })
|
||||
return state.mergeIn(['tableState'], {
|
||||
isAllSelected: true,
|
||||
selectedIds: selectedIds
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
[`${RELOAD_TABLE}_PENDING`]: (state, {payload: params}) => {
|
||||
return state.setIn(['tableState', 'selectedIds'], Map({})).setIn(['tableState', 'isLoading'], true)
|
||||
},
|
||||
|
||||
[`${RELOAD_TABLE}_FULFILLED`]: (state, {payload}) => {
|
||||
return state.mergeIn(['tableState'], {
|
||||
data: payload.data,
|
||||
count: payload.count,
|
||||
totalCount: payload.totalCount,
|
||||
isLoading: false
|
||||
})
|
||||
},
|
||||
|
||||
[TOGGLE_SIDEBAR]: (state, {payload}) => {
|
||||
const isVisible = !state.get('isSidebarVisible')
|
||||
return state.set('isSidebarVisible', isVisible)
|
||||
}
|
||||
|
||||
}, initialState)
|
||||
@@ -0,0 +1,299 @@
|
||||
import {createAction, handleActions} from 'redux-actions'
|
||||
import {fromJS} from 'immutable'
|
||||
import * as api from '../../../api/articlesApi'
|
||||
import {addAlert} from '../common/alerts'
|
||||
import {getSidebarCategories} from './sidebar'
|
||||
import {thunkAction} from '../../utils/common'
|
||||
import * as _ from 'lodash'
|
||||
|
||||
/**
|
||||
*
|
||||
* Constants
|
||||
*/
|
||||
const NS = '[Articles]'
|
||||
const SHOW_DELETE_POPUP = `${NS} Show delete popup`
|
||||
const HIDE_DELETE_POPUP = `${NS} Hide delete popup`
|
||||
const SHOW_EMAIL_POPUP = `${NS} Show email popup`
|
||||
const HIDE_EMAIL_POPUP = `${NS} Hide email popup`
|
||||
const SHOW_COMMENT_POPUP = `${NS} Show comment popup`
|
||||
const HIDE_COMMENT_POPUP = `${NS} Hide comment popup`
|
||||
const SHOW_CLIP_POPUP = `${NS} Show clip popup`
|
||||
const HIDE_CLIP_POPUP = `${NS} Hide clip popup`
|
||||
const SHOW_EMAIL_CONFIRM_POPUP = `${NS} Show email confirm popup`
|
||||
const HIDE_EMAIL_CONFIRM_POPUP = `${NS} Hide email confirm popup`
|
||||
const SET_EMAIL_PARAMS = `${NS} Set email params`
|
||||
|
||||
//This actions, when fulfilled reduced in search.js! so export this
|
||||
export const COMMENT_ARTICLE = `${NS} Comment article`
|
||||
export const UPDATE_COMMENT = `${NS} Update comment`
|
||||
export const DELETE_COMMENT = `${NS} Delete comment`
|
||||
export const LOAD_MORE_COMMENTS = `${NS} Load more comments`
|
||||
|
||||
export const DELETE_ARTICLES_FROM_FEED = `${NS} Delete articles from feed`
|
||||
export const DELETE_ARTICLES = `${NS} Delete articles from search results`
|
||||
|
||||
const EMAIL_ARTICLES = `${NS} Email articles`
|
||||
const SEND_DOCUMENTS_BY_EMAIL = `${NS} Send documents by email`
|
||||
const CLIP_ARTICLES = `${NS} Clip articles`
|
||||
const GET_RECENT_CLIP_FEEDS = `${NS} Get recent clip feeds`
|
||||
const READ_ARTICLE_LATER = `${NS} Read article later`
|
||||
const LOAD_RECIPIENTS = `${NS} Load recipients`
|
||||
|
||||
const SHOW_SHARE_MENU = `${NS} Show share menu`
|
||||
const HIDE_SHARE_MENU = `${NS} Hide share menu`
|
||||
|
||||
export const ARTICLE_COMMENTS_LIMIT = 100
|
||||
|
||||
/**
|
||||
* Actions
|
||||
*/
|
||||
const showDeleteArticlesPopup = createAction(SHOW_DELETE_POPUP, articles => articles)
|
||||
const showEmailArticlesPopup = createAction(SHOW_EMAIL_POPUP, articles => articles)
|
||||
const showCommentArticlePopup = createAction(SHOW_COMMENT_POPUP, (article, comment) => ({article, comment}))
|
||||
const showClipArticlesPopup = createAction(SHOW_CLIP_POPUP, articles => articles)
|
||||
const showEmailConfirmPopup = createAction(SHOW_EMAIL_CONFIRM_POPUP)
|
||||
const showShareMenu = createAction(SHOW_SHARE_MENU, article => article)
|
||||
const hideDeleteArticlesPopup = createAction(HIDE_DELETE_POPUP)
|
||||
const hideEmailArticlesPopup = createAction(HIDE_EMAIL_POPUP)
|
||||
const hideCommentArticlePopup = createAction(HIDE_COMMENT_POPUP)
|
||||
const hideClipArticlesPopup = createAction(HIDE_CLIP_POPUP)
|
||||
const hideEmailConfirmPopup = createAction(HIDE_EMAIL_CONFIRM_POPUP)
|
||||
const hideShareMenu = createAction(HIDE_SHARE_MENU)
|
||||
|
||||
const commentArticle = thunkAction(COMMENT_ARTICLE, (comment, articleId, {token, fulfilled}) => {
|
||||
return api
|
||||
.commentDocument(token, comment, articleId)
|
||||
.then((comment) => {
|
||||
fulfilled({comment, articleId})
|
||||
})
|
||||
})
|
||||
|
||||
const updateComment = thunkAction(UPDATE_COMMENT, (newComment, articleId, {getState, token, fulfilled}) => {
|
||||
const commentToUpdate = getState().getIn(['appState', 'articles', 'commentPopup', 'comment']).toJS()
|
||||
return api
|
||||
.updateComment(token, newComment, commentToUpdate.id)
|
||||
.then((comment) => {
|
||||
fulfilled({comment, articleId})
|
||||
})
|
||||
})
|
||||
|
||||
const deleteComment = thunkAction(DELETE_COMMENT, (commentId, articleId, {token, fulfilled}) => {
|
||||
return api
|
||||
.deleteComment(token, undefined, commentId)
|
||||
.then(() => {
|
||||
fulfilled({commentId, articleId})
|
||||
})
|
||||
})
|
||||
|
||||
const loadMoreComments = thunkAction(LOAD_MORE_COMMENTS, (articleId, offset, {token, fulfilled}) => {
|
||||
return api
|
||||
.getComments(token, {offset: offset, limit: ARTICLE_COMMENTS_LIMIT}, articleId)
|
||||
.then((response) => {
|
||||
fulfilled({response, articleId})
|
||||
})
|
||||
})
|
||||
|
||||
const deleteArticles = createAction(DELETE_ARTICLES, ids => ids)
|
||||
|
||||
const deleteArticlesFromFeed = thunkAction(DELETE_ARTICLES_FROM_FEED, (ids, feedId, {token, dispatch, fulfilled}) => {
|
||||
return api
|
||||
.deleteDocumentsFromFeed(token, ids, feedId)
|
||||
.then(() => {
|
||||
dispatch(deleteArticles(ids))
|
||||
fulfilled({ids, feedId})
|
||||
dispatch(addAlert({
|
||||
type: 'notice',
|
||||
transKey: 'articleDeleted'
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
const setEmailParams = createAction(SET_EMAIL_PARAMS, params => params)
|
||||
|
||||
const emailArticles = thunkAction(EMAIL_ARTICLES, (params, {dispatch}) => {
|
||||
dispatch(setEmailParams(params))
|
||||
if (params.subject) {
|
||||
dispatch(sendDocumentsByEmail())
|
||||
} else {
|
||||
dispatch(showEmailConfirmPopup())
|
||||
}
|
||||
})
|
||||
|
||||
const sendDocumentsByEmail = thunkAction(SEND_DOCUMENTS_BY_EMAIL, ({token, getState, fulfilled, dispatch}) => {
|
||||
const params = getState().getIn(['appState', 'articles', 'emailPopup', 'emailParams'])
|
||||
return api
|
||||
.sendDocumentsByEmail(token, params)
|
||||
.then(() => {
|
||||
dispatch(hideEmailArticlesPopup())
|
||||
fulfilled()
|
||||
})
|
||||
})
|
||||
|
||||
const clipArticles = thunkAction(CLIP_ARTICLES, (feedId, {token, fulfilled, getState, dispatch}) => {
|
||||
const articlesToClip = getState().getIn(['appState', 'articles', 'clipPopup', 'articles']).toJS()
|
||||
const documentIds = articlesToClip.map((a) => a.id)
|
||||
return api
|
||||
.clipDocuments(token, documentIds, feedId)
|
||||
.then(() => {
|
||||
dispatch(addAlert([{type: 'notice', transKey: 'clipDocument'}]))
|
||||
dispatch(hideClipArticlesPopup())
|
||||
fulfilled()
|
||||
})
|
||||
})
|
||||
|
||||
const getRecentClipFeeds = thunkAction(GET_RECENT_CLIP_FEEDS, ({token, fulfilled}) => {
|
||||
return api
|
||||
.getRecentClipFeeds(token)
|
||||
.then(fulfilled)
|
||||
})
|
||||
|
||||
const readArticleLater = thunkAction(READ_ARTICLE_LATER, (article, {token, dispatch, fulfilled}) => {
|
||||
return api
|
||||
.readLater(token, undefined, article.id)
|
||||
.then(() => {
|
||||
dispatch(addAlert([{type: 'notice', transKey: 'clipDocument'}]))
|
||||
dispatch(getSidebarCategories())
|
||||
fulfilled()
|
||||
})
|
||||
})
|
||||
|
||||
const loadRecipients = thunkAction(LOAD_RECIPIENTS, ({getState, fulfilled}) => {
|
||||
setTimeout(() => {
|
||||
const user = getState().getIn(['common', 'auth', 'user'])
|
||||
fulfilled([user.get('email')])
|
||||
}, 100)
|
||||
}, true)
|
||||
|
||||
export const actions = {
|
||||
showDeleteArticlesPopup,
|
||||
showCommentArticlePopup,
|
||||
showEmailArticlesPopup,
|
||||
showClipArticlesPopup,
|
||||
showEmailConfirmPopup,
|
||||
showShareMenu,
|
||||
hideDeleteArticlesPopup,
|
||||
hideCommentArticlePopup,
|
||||
hideEmailArticlesPopup,
|
||||
hideClipArticlesPopup,
|
||||
hideEmailConfirmPopup,
|
||||
hideShareMenu,
|
||||
commentArticle,
|
||||
updateComment,
|
||||
deleteComment,
|
||||
loadMoreComments,
|
||||
deleteArticles,
|
||||
deleteArticlesFromFeed,
|
||||
emailArticles,
|
||||
clipArticles,
|
||||
getRecentClipFeeds,
|
||||
readArticleLater,
|
||||
loadRecipients,
|
||||
sendDocumentsByEmail
|
||||
}
|
||||
|
||||
/**
|
||||
* State
|
||||
*/
|
||||
export const initialState = fromJS({
|
||||
emailPopup: {
|
||||
visible: false,
|
||||
articles: [],
|
||||
recipients: {
|
||||
all: [],
|
||||
pending: false
|
||||
},
|
||||
emailParams: null
|
||||
},
|
||||
deletePopup: {
|
||||
visible: false,
|
||||
articles: []
|
||||
},
|
||||
clipPopup: {
|
||||
visible: false,
|
||||
articles: []
|
||||
},
|
||||
commentPopup: {
|
||||
visible: false,
|
||||
article: null,
|
||||
comment: null
|
||||
},
|
||||
emailConfirmPopup: {
|
||||
visible: false
|
||||
},
|
||||
shareMenu: {
|
||||
visible: false,
|
||||
article: null
|
||||
},
|
||||
excludedArticles: [], //map of ids
|
||||
recentClipFeeds: []
|
||||
})
|
||||
|
||||
/**
|
||||
* Reducers
|
||||
*/
|
||||
const hidePopup = (type) => (state) =>
|
||||
state.mergeIn([type + 'Popup'], {visible: false, articles: []})
|
||||
|
||||
const showPopup = (type) => (state, {payload: articles}) =>
|
||||
state.mergeIn([type + 'Popup'], {visible: true, articles: articles})
|
||||
|
||||
export default handleActions({
|
||||
|
||||
[SHOW_DELETE_POPUP]: showPopup('delete'),
|
||||
[SHOW_EMAIL_POPUP]: showPopup('email'),
|
||||
[SHOW_CLIP_POPUP]: showPopup('clip'),
|
||||
[SHOW_EMAIL_CONFIRM_POPUP]: showPopup('emailConfirm'),
|
||||
[HIDE_DELETE_POPUP]: hidePopup('delete'),
|
||||
[HIDE_EMAIL_POPUP]: hidePopup('email'),
|
||||
[HIDE_CLIP_POPUP]: hidePopup('clip'),
|
||||
[HIDE_EMAIL_CONFIRM_POPUP]: hidePopup('emailConfirm'),
|
||||
|
||||
[SHOW_COMMENT_POPUP]: (state, {payload: {article, comment}}) => {
|
||||
return state.mergeIn(['commentPopup'], {
|
||||
visible: true,
|
||||
article: article,
|
||||
comment: comment
|
||||
})
|
||||
},
|
||||
|
||||
[HIDE_COMMENT_POPUP]: (state) => {
|
||||
return state.mergeIn(['commentPopup'], {
|
||||
visible: false,
|
||||
article: null,
|
||||
comment: null
|
||||
})
|
||||
},
|
||||
|
||||
[DELETE_ARTICLES]: (state, {payload: ids}) => {
|
||||
const excludedArticles = state.get('excludedArticles')
|
||||
const results = _.union(excludedArticles, ids)
|
||||
return state.set('excludedArticles', results)
|
||||
},
|
||||
|
||||
[`${GET_RECENT_CLIP_FEEDS} fulfilled`]: (state, {payload: recentClipFeeds}) => {
|
||||
return state.set('recentClipFeeds', recentClipFeeds)
|
||||
},
|
||||
|
||||
[SHOW_SHARE_MENU]: (state, {payload: article}) => {
|
||||
return state.mergeIn(['shareMenu'], {
|
||||
visible: true,
|
||||
article
|
||||
})
|
||||
},
|
||||
|
||||
[HIDE_SHARE_MENU]: (state) => {
|
||||
return state.setIn(['shareMenu', 'visible'], false)
|
||||
},
|
||||
|
||||
[`${LOAD_RECIPIENTS} pending`]: (state, {payload: {isPending}}) => {
|
||||
return state.setIn(['emailPopup', 'recipients', 'pending'], isPending)
|
||||
},
|
||||
|
||||
[`${LOAD_RECIPIENTS} fulfilled`]: (state, {payload: emails}) => {
|
||||
return state.setIn(['emailPopup', 'recipients', 'all'], emails)
|
||||
},
|
||||
|
||||
[SET_EMAIL_PARAMS]: (state, {payload: params}) => state.setIn(['emailPopup', 'emailParams'], params)
|
||||
|
||||
}, initialState)
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import * as api from '../../../api/dashboardApi'
|
||||
import ReduxModule from '../abstract/reduxModule'
|
||||
|
||||
export const LOAD_DASHBOARDS = 'Load dashboards'
|
||||
|
||||
class Dashboards extends ReduxModule {
|
||||
|
||||
getNamespace () {
|
||||
return '[Dashboard]'
|
||||
}
|
||||
|
||||
_loadDashboards ({token, fulfilled}) {
|
||||
return api
|
||||
.getDashboards(token)
|
||||
.then((dashboards) => {
|
||||
fulfilled(dashboards)
|
||||
})
|
||||
}
|
||||
|
||||
defineActions () {
|
||||
const loadDashboards = this.thunkAction(LOAD_DASHBOARDS, this._loadDashboards)
|
||||
|
||||
return {
|
||||
loadDashboards
|
||||
}
|
||||
}
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
dashboards: []
|
||||
}
|
||||
}
|
||||
|
||||
defineReducers () {
|
||||
return {
|
||||
[LOAD_DASHBOARDS]: this.setReducer('dashboards')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const dashboards = new Dashboards()
|
||||
dashboards.init()
|
||||
|
||||
export default dashboards
|
||||
@@ -0,0 +1,407 @@
|
||||
import {createAction, handleActions} from 'redux-actions'
|
||||
import {fromJS} from 'immutable'
|
||||
import * as searchApi from '../../../api/searchApi'
|
||||
import * as feedsApi from '../../../api/feedsApi'
|
||||
import {addAlert} from '../common/alerts'
|
||||
import {getRestrictions} from '../common/auth'
|
||||
import {getSidebarCategories} from './sidebar'
|
||||
import {renewSearchBy, setCommonFilters} from './searchByFilters'
|
||||
import {thunkAction, tokenInject} from '../../utils/common'
|
||||
import * as helpers from '../../utils/helpers/search'
|
||||
import { findFeedById } from '../../utils/helpers/sidebar'
|
||||
import {filtersFromServerFormat, ADV_FILTERS_LIMIT} from '../../utils/helpers/advancedFilters'
|
||||
|
||||
import {
|
||||
COMMENT_ARTICLE,
|
||||
DELETE_ARTICLES,
|
||||
DELETE_ARTICLES_FROM_FEED,
|
||||
DELETE_COMMENT,
|
||||
LOAD_MORE_COMMENTS,
|
||||
UPDATE_COMMENT
|
||||
} from './articles'
|
||||
|
||||
/*
|
||||
* Constants
|
||||
* */
|
||||
const GET_SEARCH_RESULTS = 'GET_SEARCH_RESULTS'
|
||||
|
||||
const TOGGLE_REFINE_PANEL = 'TOGGLE_REFINE_PANEL'
|
||||
const SELECT_REFINE_FILTER = 'SELECT_REFINE_FILTER'
|
||||
const CLEAR_REFINE_FILTERS = 'CLEAR_REFINE_FILTERS'
|
||||
const CLEAR_ALL_REFINE_FILTERS = 'CLEAR_ALL_REFINE_FILTERS'
|
||||
const LOAD_MORE_REFINE_FILTERS = 'LOAD_MORE_REFINE_FILTERS'
|
||||
const LOAD_LESS_REFINE_FILTERS = 'LOAD_LESS_REFINE_FILTERS'
|
||||
|
||||
const TOGGLE_SAVE_FEED_POPUP = 'TOGGLE_SAVE_FEED_POPUP'
|
||||
|
||||
const SET_FEED_RESULTS = 'SET_FEED_RESULTS'
|
||||
|
||||
const EDIT_FEED = 'EDIT_FEED'
|
||||
|
||||
const SET_NEW_SEARCH = 'SET_NEW_SEARCH'
|
||||
const CHANGE_FEED_QUERY = 'CHANGE_FEED_QUERY'
|
||||
const SET_ACTIVE_FEED = 'SET_ACTIVE_FEED'
|
||||
const CHANGE_ACTIVE_FEED_NAME = 'CHANGE_ACTIVE_FEED_NAME'
|
||||
|
||||
const SEARCH_SET_VALUE = 'SEARCH_SET_VALUE'
|
||||
|
||||
const SELECT_ARTICLE = 'SELECT_ARTICLE'
|
||||
const SELECT_ALL_ARTICLES = 'SELECT_ALL_ARTICLES'
|
||||
const SAVE_FEED = 'SAVE_FEED'
|
||||
const SAVE_AS_FEED = 'SAVE_AS_FEED'
|
||||
|
||||
/*
|
||||
* Actions
|
||||
* */
|
||||
const getSearchResultsPending = createAction(GET_SEARCH_RESULTS + '_PENDING')
|
||||
const getSearchResultsRejected = createAction(GET_SEARCH_RESULTS + '_REJECTED', (errors) => errors)
|
||||
const setFeedResults = createAction(SET_FEED_RESULTS, (response) => response)
|
||||
|
||||
const _getSearchResults = (dispatch, getState, apiPromise, initialSearch = false) => {
|
||||
dispatch(getSearchResultsPending())
|
||||
return apiPromise
|
||||
.then((response) => {
|
||||
dispatch(setFeedResults(response))
|
||||
if (response.meta.search.filters) {
|
||||
dispatch(setCommonFilters(response.meta.search.filters, response.meta.sourceLists, response.meta.sources))
|
||||
} else {
|
||||
dispatch(renewSearchBy())
|
||||
}
|
||||
if (response.feed) {
|
||||
const categories = getState().getIn(['appState', 'sidebar', 'categories']).toJS()
|
||||
const feed = findFeedById(categories, parseInt(response.feed))
|
||||
dispatch(setActiveFeed(fromJS(feed)))
|
||||
}
|
||||
const isFreeUser = getState().getIn(['common', 'auth', 'user', 'restrictions', 'plans', 'price']) === 0;
|
||||
if (initialSearch && isFreeUser) {
|
||||
const email = getState().getIn(['common', 'auth', 'user', 'email'])
|
||||
searchApi.submitSearchHubspot({
|
||||
email: email,
|
||||
searchquery: response.meta.search.query
|
||||
});
|
||||
}
|
||||
dispatch(getRestrictions())
|
||||
})
|
||||
.catch((errors) => {
|
||||
dispatch(getSearchResultsRejected(errors))
|
||||
dispatch(addAlert(errors))
|
||||
})
|
||||
}
|
||||
|
||||
const getSearchResults = (data, initialSearch) => {
|
||||
return tokenInject((dispatch, getState, token) => {
|
||||
const apiPromise = searchApi.searchQuery(token, data)
|
||||
_getSearchResults(dispatch, getState, apiPromise, initialSearch)
|
||||
})
|
||||
}
|
||||
|
||||
const getFeedResults = (data, feedId) => {
|
||||
return tokenInject((dispatch, getState, token) => {
|
||||
const apiPromise = feedsApi.getFeedSearchResults(token, data, feedId)
|
||||
_getSearchResults(dispatch, getState, apiPromise)
|
||||
})
|
||||
}
|
||||
|
||||
const saveAsFeed = thunkAction(SAVE_AS_FEED, (dataToSend, {token, dispatch, fulfilled}) => {
|
||||
return feedsApi
|
||||
.createFeed(token, dataToSend)
|
||||
.then(() => {
|
||||
fulfilled()
|
||||
dispatch(getSidebarCategories())
|
||||
dispatch(getRestrictions())
|
||||
dispatch(addAlert({
|
||||
type: 'notice',
|
||||
transKey: 'saveFeed',
|
||||
id: 'saveFeed'
|
||||
}))
|
||||
})
|
||||
}, true)
|
||||
|
||||
const saveFeed = thunkAction(SAVE_FEED, (dataToSend, feedId, {token, dispatch, fulfilled}) => {
|
||||
return feedsApi
|
||||
.saveFeed(token, dataToSend, feedId)
|
||||
.then(() => {
|
||||
fulfilled()
|
||||
dispatch(getSidebarCategories())
|
||||
dispatch(getRestrictions())
|
||||
dispatch(addAlert({
|
||||
type: 'notice',
|
||||
transKey: 'saveFeed',
|
||||
id: 'saveFeed'
|
||||
}))
|
||||
})
|
||||
}, true)
|
||||
|
||||
const toggleRefinePanel = createAction(TOGGLE_REFINE_PANEL)
|
||||
const selectRefineFilter = createAction(SELECT_REFINE_FILTER, (groupName, filterValue) => {
|
||||
return {groupName, filterValue}
|
||||
})
|
||||
const clearRefineFilters = createAction(CLEAR_REFINE_FILTERS)
|
||||
const clearAllRefineFilters = createAction(CLEAR_ALL_REFINE_FILTERS)
|
||||
const loadMoreRefineFilters = createAction(LOAD_MORE_REFINE_FILTERS, groupName => groupName)
|
||||
const loadLessRefineFilters = createAction(LOAD_LESS_REFINE_FILTERS, groupName => groupName)
|
||||
|
||||
const toggleSaveFeedPopup = createAction(TOGGLE_SAVE_FEED_POPUP)
|
||||
|
||||
const editFeed = createAction(EDIT_FEED)
|
||||
const setNewSearch = createAction(SET_NEW_SEARCH)
|
||||
const changeFeedQuery = createAction(CHANGE_FEED_QUERY, value => value)
|
||||
|
||||
const setActiveFeed = createAction(SET_ACTIVE_FEED, feed => feed)
|
||||
|
||||
const changeActiveFeedName = createAction(CHANGE_ACTIVE_FEED_NAME, feedName => feedName)
|
||||
|
||||
const selectArticle = createAction(SELECT_ARTICLE, article => article)
|
||||
const selectAllArticles = createAction(SELECT_ALL_ARTICLES, select => select)
|
||||
|
||||
export const actions = {
|
||||
getSearchResults,
|
||||
toggleSaveFeedPopup,
|
||||
toggleRefinePanel,
|
||||
loadMoreRefineFilters,
|
||||
loadLessRefineFilters,
|
||||
selectRefineFilter,
|
||||
clearRefineFilters,
|
||||
clearAllRefineFilters,
|
||||
setFeedResults,
|
||||
editFeed,
|
||||
setNewSearch,
|
||||
saveAsFeed,
|
||||
saveFeed,
|
||||
getFeedResults,
|
||||
setActiveFeed,
|
||||
changeActiveFeedName,
|
||||
changeFeedQuery,
|
||||
selectArticle,
|
||||
selectAllArticles
|
||||
}
|
||||
|
||||
/*
|
||||
* State
|
||||
* */
|
||||
export const initialState = fromJS({
|
||||
isEditingFeed: false,
|
||||
isSavingFeed: false,
|
||||
activeFeed: null,
|
||||
loadedFeedName: null,
|
||||
loadedFeedQuery: null,
|
||||
searchResults: [],
|
||||
searchResultsErrors: [],
|
||||
searchResultsPending: false,
|
||||
searchResultCount: 0,
|
||||
searchResultTotalCount: 0,
|
||||
searchResultPage: 1,
|
||||
searchResultLimit: 100,
|
||||
isSavedSearchVisible: false,
|
||||
isSaveFeedPopupVisible: false,
|
||||
isSynced: true,
|
||||
isLoaded: false,
|
||||
advancedFilters: {
|
||||
all: {},
|
||||
pages: {}, //{groupName1: {count: xx, totalCount: yy}, groupName2: ....}
|
||||
selected: {}, // {keyword: {"fsdfdsf": 1}, groupName1: {value1: 0, value2: 1, ....}, groupName2: {}, ... } will be sent to the server
|
||||
pending: {}, //which groups is not applied yet
|
||||
isVisible: true
|
||||
},
|
||||
selectedArticles: []
|
||||
})
|
||||
|
||||
const deselectArticlesReducer = (state, {payload: ids}) => {
|
||||
const selectedArticles = state.get('selectedArticles').toJS()
|
||||
const filtered = selectedArticles.filter((article) => !ids.includes(article.id))
|
||||
return state.set('selectedArticles', fromJS(filtered))
|
||||
}
|
||||
/*
|
||||
* Reducers
|
||||
* */
|
||||
export default handleActions({
|
||||
|
||||
[SEARCH_SET_VALUE]: (state, {payload}) => {
|
||||
const {field, value} = payload
|
||||
return state.set(field, value)
|
||||
},
|
||||
|
||||
[`${GET_SEARCH_RESULTS}_PENDING`]: (state) => {
|
||||
return state.merge({
|
||||
searchResults: [],
|
||||
searchResultCount: 0,
|
||||
searchResultTotalCount: 0,
|
||||
searchResultPage: 1,
|
||||
searchResultsPending: true
|
||||
})
|
||||
},
|
||||
|
||||
[`${GET_SEARCH_RESULTS}_REJECTED`]: (state, {payload}) => {
|
||||
return state.merge({
|
||||
searchResults: [],
|
||||
searchResultsErrors: payload,
|
||||
searchResultCount: 0,
|
||||
searchResultTotalCount: 0,
|
||||
searchResultsPending: false
|
||||
})
|
||||
},
|
||||
|
||||
[SET_FEED_RESULTS]: (state, {payload: response}) => {
|
||||
const {documents, advancedFilters, meta} = response
|
||||
|
||||
const selectedFilters = meta.search.advancedFilters
|
||||
const {allFilters, pages} = filtersFromServerFormat(advancedFilters)
|
||||
|
||||
delete allFilters.reach // to hide reach in advanced filters
|
||||
|
||||
helpers.mergeAdvancedFilters(allFilters, selectedFilters, pages)
|
||||
|
||||
return state
|
||||
.merge({
|
||||
searchResults: documents.data,
|
||||
searchResultsErrors: [],
|
||||
searchResultCount: documents.count,
|
||||
searchResultTotalCount: documents.totalCount,
|
||||
searchResultPage: documents.page,
|
||||
searchResultLimit: documents.limit,
|
||||
searchResultsPending: false,
|
||||
selectedArticles: [],
|
||||
loadedFeedQuery: meta.search.query,
|
||||
isSynced: meta.status === 'synced',
|
||||
isLoaded: true,
|
||||
isEditingFeed: true
|
||||
})
|
||||
.mergeIn(['advancedFilters'], {
|
||||
all: allFilters,
|
||||
selected: selectedFilters,
|
||||
pages: pages,
|
||||
pending: {}
|
||||
})
|
||||
},
|
||||
|
||||
[SET_ACTIVE_FEED]: (state, {payload: feed}) => {
|
||||
return state.merge({
|
||||
'activeFeed': feed,
|
||||
'isEditingFeed': false
|
||||
})
|
||||
},
|
||||
|
||||
[CHANGE_ACTIVE_FEED_NAME]: (state, {payload: feedName}) => {
|
||||
return state.setIn(['activeFeed', 'name'], feedName)
|
||||
},
|
||||
|
||||
[TOGGLE_SAVE_FEED_POPUP]: (state, {payload}) => {
|
||||
const isVisible = !state.get('isSaveFeedPopupVisible')
|
||||
return state.set('isSaveFeedPopupVisible', isVisible)
|
||||
},
|
||||
|
||||
[TOGGLE_REFINE_PANEL]: (state) => {
|
||||
const path = ['advancedFilters', 'isVisible']
|
||||
return state.setIn(path, !state.getIn(path))
|
||||
},
|
||||
|
||||
[SELECT_REFINE_FILTER]: (state, {payload: {groupName, filterValue}}) => {
|
||||
const path = ['advancedFilters', 'selected', groupName]
|
||||
//two groups without multiple selection
|
||||
if (groupName === 'articleDate' || groupName === 'keyword') {
|
||||
state = state.deleteIn(path)
|
||||
}
|
||||
//tri-state switch
|
||||
const currentState = state.getIn([...path, filterValue])
|
||||
let newState
|
||||
if (currentState === undefined) {
|
||||
newState = 1
|
||||
}
|
||||
else if (currentState === 1) {
|
||||
newState = -1
|
||||
}
|
||||
return state.deleteIn(['advancedFilters', 'pending', groupName]).setIn([...path, filterValue], newState)
|
||||
},
|
||||
|
||||
[CLEAR_REFINE_FILTERS]: (state, {payload: groupName}) => {
|
||||
return state.setIn(['advancedFilters', 'pending', groupName], true).deleteIn(['advancedFilters', 'selected', groupName])
|
||||
},
|
||||
|
||||
[CLEAR_ALL_REFINE_FILTERS]: (state) => {
|
||||
return state.mergeIn(['advancedFilters'], {
|
||||
selected: {},
|
||||
pending: {}
|
||||
})
|
||||
},
|
||||
|
||||
[LOAD_LESS_REFINE_FILTERS]: (state, {payload: groupName}) => {
|
||||
const path = ['advancedFilters', 'pages', groupName, 'count']
|
||||
const currentCount = state.getIn(path)
|
||||
return state.setIn(path, Math.max(currentCount - ADV_FILTERS_LIMIT, ADV_FILTERS_LIMIT))
|
||||
},
|
||||
|
||||
[LOAD_MORE_REFINE_FILTERS]: (state, {payload: groupName}) => {
|
||||
const path = ['advancedFilters', 'pages', groupName]
|
||||
const currentCount = state.getIn([...path, 'count'])
|
||||
const totalCount = state.getIn([...path, 'totalCount'])
|
||||
return state.setIn([...path, 'count'], Math.min(currentCount + ADV_FILTERS_LIMIT, totalCount))
|
||||
},
|
||||
|
||||
[EDIT_FEED]: (state) => {
|
||||
return state.set('isEditingFeed', true)
|
||||
},
|
||||
|
||||
[SET_NEW_SEARCH]: (state) => {
|
||||
return state.merge(initialState.toJS())
|
||||
},
|
||||
|
||||
[CHANGE_FEED_QUERY]: (state, {payload: value}) => {
|
||||
return state.merge({
|
||||
loadedFeedQuery: value,
|
||||
isEditingFeed: true
|
||||
})
|
||||
},
|
||||
|
||||
[`${SAVE_FEED} pending`]: (state, {payload: {isPending}}) => {
|
||||
return state.set('isSavingFeed', isPending)
|
||||
},
|
||||
|
||||
[`${SAVE_AS_FEED} pending`]: (state, {payload: {isPending}}) => {
|
||||
return state.set('isSavingFeed', isPending)
|
||||
},
|
||||
|
||||
[SELECT_ARTICLE]: (state, {payload: article}) => {
|
||||
let selectedArticles = state.get('selectedArticles').toJS()
|
||||
const articleIndex = helpers.indexById(selectedArticles, article.id)
|
||||
if (articleIndex === -1) { //not selected yet
|
||||
selectedArticles = selectedArticles.concat(article)
|
||||
} else {
|
||||
selectedArticles.splice(articleIndex, 1)
|
||||
}
|
||||
return state.set('selectedArticles', fromJS(selectedArticles))
|
||||
},
|
||||
|
||||
[SELECT_ALL_ARTICLES]: (state, {payload: select}) => {
|
||||
const selected = select ? state.get('searchResults').toJS() : fromJS([])
|
||||
return state.set('selectedArticles', selected)
|
||||
},
|
||||
|
||||
[`${LOAD_MORE_COMMENTS} fulfilled`]: (state, {payload}) => {
|
||||
const {articleId, response} = payload
|
||||
const articles = state.get('searchResults')
|
||||
return state.set('searchResults', helpers.loadMoreComments(articles, articleId, response.data))
|
||||
},
|
||||
|
||||
[`${COMMENT_ARTICLE} fulfilled`]: (state, {payload}) => {
|
||||
const {comment, articleId} = payload
|
||||
const articles = state.get('searchResults')
|
||||
return state.set('searchResults', helpers.addComment(articles, articleId, comment))
|
||||
},
|
||||
|
||||
[`${UPDATE_COMMENT} fulfilled`]: (state, {payload}) => {
|
||||
const {comment, articleId} = payload
|
||||
const articles = state.get('searchResults')
|
||||
return state.set('searchResults', helpers.updateComment(articles, articleId, comment))
|
||||
},
|
||||
|
||||
[`${DELETE_COMMENT} fulfilled`]: (state, {payload}) => {
|
||||
const {commentId, articleId} = payload
|
||||
const articles = state.get('searchResults')
|
||||
return state.set('searchResults', helpers.deleteComment(articles, articleId, commentId))
|
||||
},
|
||||
|
||||
//only remove deleted articles the from selection
|
||||
[DELETE_ARTICLES]: deselectArticlesReducer,
|
||||
[`${DELETE_ARTICLES_FROM_FEED} fulfilled`]: deselectArticlesReducer
|
||||
|
||||
}, initialState)
|
||||
@@ -0,0 +1,560 @@
|
||||
import { createAction, handleActions } from 'redux-actions'
|
||||
import { fromJS } from 'immutable'
|
||||
|
||||
import { searchSources, getSourceLists } from '../../../api/searchApi'
|
||||
import {thunkAction} from '../../utils/common'
|
||||
import { storeObj } from '../../../main'
|
||||
/*
|
||||
* Constants
|
||||
* */
|
||||
export const TOGGLE_MEDIA_TYPE = 'TOGGLE_MEDIA_TYPE'
|
||||
export const TOGGLE_ALL_MEDIA_TYPES = 'TOGGLE_ALL_MEDIA_TYPES'
|
||||
|
||||
export const SET_SEARCH_INTERVAL = 'SET_SEARCH_INTERVAL'
|
||||
export const SET_SEARCH_LAST_DATE = 'SET_SEARCH_LAST_DATE'
|
||||
export const SET_SEARCH_DATE = 'SET_SEARCH_DATE'
|
||||
export const SET_START_DATE = 'SET_START_DATE'
|
||||
export const SET_END_DATE = 'SET_END_DATE'
|
||||
|
||||
export const TOGGLE_SEARCH_BY = 'TOGGLE_SEARCH_BY'
|
||||
export const CHOOSE_SEARCH_BY_TAB = 'CHOOSE_SEARCH_BY_TAB'
|
||||
|
||||
export const SET_HEADLINE_INCLUDED = 'SET_HEADLINE_INCLUDED'
|
||||
export const SET_HEADLINE_EXCLUDED = 'SET_HEADLINE_EXCLUDED'
|
||||
|
||||
export const TOGGLE_LANG = 'TOGGLE_LANG'
|
||||
export const TOGGLE_ALL_LANGS = 'TOGGLE_ALL_LANGS'
|
||||
|
||||
export const CHANGE_LOCATIONS_TYPE = 'CHANGE_LOCATIONS_TYPE'
|
||||
|
||||
export const MOVE_LOCATION = 'MOVE_LOCATION'
|
||||
|
||||
export const CLEAR_LOCATIONS = 'CLEAR_LOCATIONS'
|
||||
|
||||
export const GET_SEARCH_BY_SOURCES = 'GET_SEARCH_BY_SOURCES'
|
||||
export const SET_SEARCH_BY_SOURCES_QUERY = 'SET_SEARCH_BY_SOURCES_QUERY'
|
||||
|
||||
export const ADD_SELECTED_SEARCH_BY_SOURCE = 'ADD_SELECTED_SEARCH_BY_SOURCE'
|
||||
export const REMOVE_SELECTED_SEARCH_BY_SOURCE = 'REMOVE_SELECTED_SEARCH_BY_SOURCE'
|
||||
|
||||
export const INCLUDE_EXCLUDE_SEARCH_BY_SOURCES = 'INCLUDE_EXCLUDE_SEARCH_BY_SOURCES'
|
||||
export const CLEAR_SEARCH_BY_SOURCES = 'CLEAR_SEARCH_BY_SOURCES'
|
||||
|
||||
export const GET_SEARCH_BY_SOURCE_LISTS = 'GET_SEARCH_BY_SOURCE_LISTS'
|
||||
|
||||
export const MOVE_SOURCE_LIST = 'MOVE_SOURCE_LIST'
|
||||
|
||||
export const TOGGLE_INCLUDE_DUPLICATES = 'TOGGLE_INCLUDE_DUPLICATES'
|
||||
export const TOGGLE_HAS_IMAGES = 'TOGGLE_HAS_IMAGES'
|
||||
|
||||
export const RENEW_SEARCH_BY = 'RENEW_SEARCH_BY'
|
||||
export const SET_COMMON_FILTERS = 'SET_COMMON_FILTERS'
|
||||
|
||||
/*
|
||||
* Actions
|
||||
* */
|
||||
export const toggleMediaType = createAction(TOGGLE_MEDIA_TYPE, (chosenType, isChosen) => {
|
||||
return {chosenType, isChosen}
|
||||
})
|
||||
export const toggleAllMediaTypes = createAction(TOGGLE_ALL_MEDIA_TYPES, isChosen => isChosen)
|
||||
|
||||
export const setSearchInterval = createAction(SET_SEARCH_INTERVAL, (newInterval) => newInterval)
|
||||
export const setSearchLastDate = createAction(SET_SEARCH_LAST_DATE, (newLastDate) => newLastDate)
|
||||
export const setSearchDate = createAction(SET_SEARCH_DATE, (newDate) => newDate)
|
||||
export const setStartDate = createAction(SET_START_DATE, (newDate) => newDate)
|
||||
export const setEndDate = createAction(SET_END_DATE, (newDate) => newDate)
|
||||
|
||||
export const toggleSearchBy = createAction(TOGGLE_SEARCH_BY)
|
||||
export const chooseSearchByTab = createAction(CHOOSE_SEARCH_BY_TAB, (tabName) => tabName)
|
||||
|
||||
export const setHeadlineIncluded = createAction(SET_HEADLINE_INCLUDED, (headline) => headline)
|
||||
export const setHeadlineExcluded = createAction(SET_HEADLINE_EXCLUDED, (headline) => headline)
|
||||
|
||||
export const toggleLang = createAction(TOGGLE_LANG, (chosenLang, isChosen) => {
|
||||
return {chosenLang, isChosen}
|
||||
})
|
||||
export const toggleAllLangs = createAction(TOGGLE_ALL_LANGS, isChosen => isChosen)
|
||||
|
||||
export const changeLocationsType = createAction(CHANGE_LOCATIONS_TYPE, (newLocationsType) => newLocationsType)
|
||||
|
||||
export const moveLocation = createAction(MOVE_LOCATION, (from, to, locType, loc) => {
|
||||
return {from, to, locType, loc}
|
||||
})
|
||||
|
||||
export const clearLocations = createAction(CLEAR_LOCATIONS)
|
||||
|
||||
export const getSearchBySources = thunkAction(GET_SEARCH_BY_SOURCES, (data, {token, fulfilled}) => {
|
||||
return searchSources(token, data)
|
||||
.then((data) => {
|
||||
fulfilled(data)
|
||||
})
|
||||
})
|
||||
export const setSearchBySourcesQuery = createAction(SET_SEARCH_BY_SOURCES_QUERY, (query) => query)
|
||||
|
||||
export const addSelectedSearchBySource = createAction(ADD_SELECTED_SEARCH_BY_SOURCE, (source) => source)
|
||||
|
||||
export const removeSelectedSearchBySource = createAction(REMOVE_SELECTED_SEARCH_BY_SOURCE, (sourceId) => sourceId)
|
||||
export const clearSearchBySources = createAction(CLEAR_SEARCH_BY_SOURCES)
|
||||
|
||||
export const includeExcludeSearchBySources = createAction(INCLUDE_EXCLUDE_SEARCH_BY_SOURCES, (sourcesType) => sourcesType)
|
||||
|
||||
export const getSearchBySourceLists = thunkAction(GET_SEARCH_BY_SOURCE_LISTS, (data, {token, fulfilled}) => {
|
||||
return getSourceLists(token, data)
|
||||
.then((data) => {
|
||||
fulfilled(data)
|
||||
})
|
||||
})
|
||||
|
||||
export const moveSourceList = createAction(MOVE_SOURCE_LIST, (from, to, list) => {
|
||||
return {from, to, list}
|
||||
})
|
||||
|
||||
export const toggleIncludeDuplicates = createAction(TOGGLE_INCLUDE_DUPLICATES)
|
||||
export const toggleHasImages = createAction(TOGGLE_HAS_IMAGES)
|
||||
|
||||
export const renewSearchBy = createAction(RENEW_SEARCH_BY)
|
||||
|
||||
export const setCommonFilters = createAction(SET_COMMON_FILTERS, (filters, sourceLists, sources) => {
|
||||
return {filters, sourceLists, sources}
|
||||
})
|
||||
|
||||
export const actions = {
|
||||
toggleMediaType,
|
||||
toggleAllMediaTypes,
|
||||
setSearchInterval,
|
||||
setSearchLastDate,
|
||||
setSearchDate,
|
||||
setStartDate,
|
||||
setEndDate,
|
||||
toggleSearchBy,
|
||||
chooseSearchByTab,
|
||||
setHeadlineIncluded,
|
||||
setHeadlineExcluded,
|
||||
toggleLang,
|
||||
toggleAllLangs,
|
||||
changeLocationsType,
|
||||
moveLocation,
|
||||
clearLocations,
|
||||
getSearchBySources,
|
||||
setSearchBySourcesQuery,
|
||||
addSelectedSearchBySource,
|
||||
removeSelectedSearchBySource,
|
||||
clearSearchBySources,
|
||||
includeExcludeSearchBySources,
|
||||
getSearchBySourceLists,
|
||||
moveSourceList,
|
||||
toggleIncludeDuplicates,
|
||||
toggleHasImages,
|
||||
renewSearchBy,
|
||||
setCommonFilters
|
||||
}
|
||||
|
||||
/*
|
||||
* State
|
||||
* */
|
||||
const locations = [{code: 'AD', type: 'country'}, {code: 'AE', type: 'country'}, {code: 'AF', type: 'country'}, {code: 'AG', type: 'country'}, {code: 'AI', type: 'country'}, {code: 'AL', type: 'country'}, {code: 'AM', type: 'country'}, {code: 'AO', type: 'country'}, {code: 'AQ', type: 'country'}, {code: 'AR', type: 'country'}, {code: 'AS', type: 'country'},
|
||||
{code: 'AT', type: 'country'}, {code: 'AU', type: 'country'}, {code: 'AW', type: 'country'}, {code: 'AX', type: 'country'}, {code: 'AZ', type: 'country'}, {code: 'BA', type: 'country'}, {code: 'BB', type: 'country'}, {code: 'BD', type: 'country'}, {code: 'BE', type: 'country'}, {code: 'BF', type: 'country'}, {code: 'BG', type: 'country'}, {code: 'BH', type: 'country'}, {code: 'BI', type: 'country'}, {code: 'BJ', type: 'country'},
|
||||
{code: 'BL', type: 'country'}, {code: 'BM', type: 'country'}, {code: 'BN', type: 'country'}, {code: 'BO', type: 'country'}, {code: 'BQ', type: 'country'}, {code: 'BR', type: 'country'}, {code: 'BS', type: 'country'}, {code: 'BT', type: 'country'}, {code: 'BV', type: 'country'}, {code: 'BW', type: 'country'}, {code: 'BY', type: 'country'}, {code: 'BZ', type: 'country'}, {code: 'CA', type: 'country'}, {code: 'CC', type: 'country'},
|
||||
{code: 'CD', type: 'country'}, {code: 'CF', type: 'country'}, {code: 'CG', type: 'country'}, {code: 'CH', type: 'country'}, {code: 'CI', type: 'country'}, {code: 'CK', type: 'country'}, {code: 'CL', type: 'country'}, {code: 'CM', type: 'country'}, {code: 'CN', type: 'country'}, {code: 'CO', type: 'country'}, {code: 'CR', type: 'country'}, {code: 'CU', type: 'country'}, {code: 'CV', type: 'country'}, {code: 'CW', type: 'country'},
|
||||
{code: 'CX', type: 'country'}, {code: 'CY', type: 'country'}, {code: 'CZ', type: 'country'}, {code: 'DE', type: 'country'}, {code: 'DJ', type: 'country'}, {code: 'DK', type: 'country'}, {code: 'DM', type: 'country'}, {code: 'DO', type: 'country'}, {code: 'DZ', type: 'country'}, {code: 'EC', type: 'country'}, {code: 'EE', type: 'country'}, {code: 'EG', type: 'country'}, {code: 'EH', type: 'country'}, {code: 'ER', type: 'country'},
|
||||
{code: 'ES', type: 'country'}, {code: 'ET', type: 'country'}, {code: 'FI', type: 'country'}, {code: 'FJ', type: 'country'}, {code: 'FK', type: 'country'}, {code: 'FM', type: 'country'}, {code: 'FO', type: 'country'}, {code: 'FR', type: 'country'}, {code: 'GA', type: 'country'}, {code: 'GB', type: 'country'}, {code: 'GD', type: 'country'}, {code: 'GE', type: 'country'}, {code: 'GF', type: 'country'}, {code: 'GG', type: 'country'},
|
||||
{code: 'GH', type: 'country'}, {code: 'GI', type: 'country'}, {code: 'GL', type: 'country'}, {code: 'GM', type: 'country'}, {code: 'GN', type: 'country'}, {code: 'GP', type: 'country'}, {code: 'GQ', type: 'country'}, {code: 'GR', type: 'country'}, {code: 'GS', type: 'country'}, {code: 'GT', type: 'country'}, {code: 'GU', type: 'country'}, {code: 'GW', type: 'country'}, {code: 'GY', type: 'country'}, {code: 'HK', type: 'country'},
|
||||
{code: 'HM', type: 'country'}, {code: 'HN', type: 'country'}, {code: 'HR', type: 'country'}, {code: 'HT', type: 'country'}, {code: 'HU', type: 'country'}, {code: 'ID', type: 'country'}, {code: 'IE', type: 'country'}, {code: 'IL', type: 'country'}, {code: 'IM', type: 'country'}, {code: 'IN', type: 'country'}, {code: 'IO', type: 'country'}, {code: 'IQ', type: 'country'}, {code: 'IR', type: 'country'}, {code: 'IS', type: 'country'},
|
||||
{code: 'IT', type: 'country'}, {code: 'JE', type: 'country'}, {code: 'JM', type: 'country'}, {code: 'JO', type: 'country'}, {code: 'JP', type: 'country'}, {code: 'KE', type: 'country'}, {code: 'KG', type: 'country'}, {code: 'KH', type: 'country'}, {code: 'KI', type: 'country'}, {code: 'KM', type: 'country'}, {code: 'KN', type: 'country'}, {code: 'KP', type: 'country'}, {code: 'KR', type: 'country'}, {code: 'KW', type: 'country'},
|
||||
{code: 'KY', type: 'country'}, {code: 'KZ', type: 'country'}, {code: 'LA', type: 'country'}, {code: 'LB', type: 'country'}, {code: 'LC', type: 'country'}, {code: 'LI', type: 'country'}, {code: 'LK', type: 'country'}, {code: 'LR', type: 'country'}, {code: 'LS', type: 'country'}, {code: 'LT', type: 'country'}, {code: 'LU', type: 'country'}, {code: 'LV', type: 'country'}, {code: 'LY', type: 'country'}, {code: 'MA', type: 'country'},
|
||||
{code: 'MC', type: 'country'}, {code: 'MD', type: 'country'}, {code: 'ME', type: 'country'}, {code: 'MF', type: 'country'}, {code: 'MG', type: 'country'}, {code: 'MH', type: 'country'}, {code: 'MK', type: 'country'}, {code: 'ML', type: 'country'}, {code: 'MM', type: 'country'}, {code: 'MN', type: 'country'}, {code: 'MO', type: 'country'}, {code: 'MP', type: 'country'}, {code: 'MQ', type: 'country'}, {code: 'MR', type: 'country'},
|
||||
{code: 'MS', type: 'country'}, {code: 'MT', type: 'country'}, {code: 'MU', type: 'country'}, {code: 'MV', type: 'country'}, {code: 'MW', type: 'country'}, {code: 'MX', type: 'country'}, {code: 'MY', type: 'country'}, {code: 'MZ', type: 'country'}, {code: 'NA', type: 'country'}, {code: 'NC', type: 'country'}, {code: 'NE', type: 'country'}, {code: 'NF', type: 'country'}, {code: 'NG', type: 'country'}, {code: 'NI', type: 'country'},
|
||||
{code: 'NL', type: 'country'}, {code: 'NO', type: 'country'}, {code: 'NP', type: 'country'}, {code: 'NR', type: 'country'}, {code: 'NU', type: 'country'}, {code: 'NZ', type: 'country'}, {code: 'OM', type: 'country'}, {code: 'PA', type: 'country'}, {code: 'PE', type: 'country'}, {code: 'PF', type: 'country'}, {code: 'PG', type: 'country'}, {code: 'PH', type: 'country'}, {code: 'PK', type: 'country'}, {code: 'PL', type: 'country'},
|
||||
{code: 'PM', type: 'country'}, {code: 'PN', type: 'country'}, {code: 'PR', type: 'country'}, {code: 'PS', type: 'country'}, {code: 'PT', type: 'country'}, {code: 'PW', type: 'country'}, {code: 'PY', type: 'country'}, {code: 'QA', type: 'country'}, {code: 'RE', type: 'country'}, {code: 'RO', type: 'country'}, {code: 'RS', type: 'country'}, {code: 'RU', type: 'country'}, {code: 'RW', type: 'country'}, {code: 'SA', type: 'country'},
|
||||
{code: 'SB', type: 'country'}, {code: 'SC', type: 'country'}, {code: 'SD', type: 'country'}, {code: 'SE', type: 'country'}, {code: 'SG', type: 'country'}, {code: 'SH', type: 'country'}, {code: 'SI', type: 'country'}, {code: 'SJ', type: 'country'}, {code: 'SK', type: 'country'}, {code: 'SL', type: 'country'}, {code: 'SM', type: 'country'}, {code: 'SN', type: 'country'}, {code: 'SO', type: 'country'}, {code: 'SR', type: 'country'},
|
||||
{code: 'SS', type: 'country'}, {code: 'ST', type: 'country'}, {code: 'SV', type: 'country'}, {code: 'SX', type: 'country'}, {code: 'SY', type: 'country'}, {code: 'SZ', type: 'country'}, {code: 'TC', type: 'country'}, {code: 'TD', type: 'country'}, {code: 'TF', type: 'country'}, {code: 'TG', type: 'country'}, {code: 'TH', type: 'country'}, {code: 'TJ', type: 'country'}, {code: 'TK', type: 'country'}, {code: 'TL', type: 'country'},
|
||||
{code: 'TM', type: 'country'}, {code: 'TN', type: 'country'}, {code: 'TO', type: 'country'}, {code: 'TR', type: 'country'}, {code: 'TT', type: 'country'}, {code: 'TV', type: 'country'}, {code: 'TW', type: 'country'}, {code: 'TZ', type: 'country'}, {code: 'UA', type: 'country'}, {code: 'UG', type: 'country'}, {code: 'UM', type: 'country'}, {code: 'US', type: 'country'}, {code: 'UY', type: 'country'}, {code: 'UZ', type: 'country'},
|
||||
{code: 'VA', type: 'country'}, {code: 'VC', type: 'country'}, {code: 'VE', type: 'country'}, {code: 'VG', type: 'country'}, {code: 'VI', type: 'country'}, {code: 'VN', type: 'country'}, {code: 'VU', type: 'country'}, {code: 'WF', type: 'country'}, {code: 'WS', type: 'country'}, {code: 'YE', type: 'country'}, {code: 'YT', type: 'country'}, {code: 'ZA', type: 'country'}, {code: 'ZM', type: 'country'}, {code: 'ZW', type: 'country'}, {code: 'AL', type: 'state'}, {code: 'AK', type: 'state'}, {code: 'AZ', type: 'state'}, {code: 'AR', type: 'state'}, {code: 'CA', type: 'state'}, {code: 'CO', type: 'state'}, {code: 'CT', type: 'state'}, {code: 'DE', type: 'state'}, {code: 'DC', type: 'state'}, {code: 'FL', type: 'state'}, {code: 'GA', type: 'state'}, {code: 'HI', type: 'state'},
|
||||
{code: 'ID', type: 'state'}, {code: 'IL', type: 'state'}, {code: 'IN', type: 'state'}, {code: 'IA', type: 'state'}, {code: 'KS', type: 'state'}, {code: 'KY', type: 'state'}, {code: 'LA', type: 'state'}, {code: 'ME', type: 'state'}, {code: 'MD', type: 'state'}, {code: 'MA', type: 'state'}, {code: 'MI', type: 'state'}, {code: 'MN', type: 'state'}, {code: 'MS', type: 'state'}, {code: 'MO', type: 'state'},
|
||||
{code: 'MT', type: 'state'}, {code: 'NE', type: 'state'}, {code: 'NV', type: 'state'}, {code: 'NH', type: 'state'}, {code: 'NJ', type: 'state'}, {code: 'NM', type: 'state'}, {code: 'NY', type: 'state'}, {code: 'NC', type: 'state'}, {code: 'ND', type: 'state'}, {code: 'OH', type: 'state'}, {code: 'OK', type: 'state'}, {code: 'OR', type: 'state'}, {code: 'PA', type: 'state'}, {code: 'RI', type: 'state'},
|
||||
{code: 'SC', type: 'state'}, {code: 'SD', type: 'state'}, {code: 'TN', type: 'state'}, {code: 'TX', type: 'state'}, {code: 'UT', type: 'state'}, {code: 'VT', type: 'state'}, {code: 'VA', type: 'state'}, {code: 'WA', type: 'state'}, {code: 'WV', type: 'state'}, {code: 'WI', type: 'state'}, {code: 'WY', type: 'state'}]
|
||||
|
||||
export const allMediaTypes = [ // last 3 are domain params
|
||||
'news',
|
||||
'blogs',
|
||||
'reddit',
|
||||
'twitter',
|
||||
'instagram'
|
||||
];
|
||||
|
||||
export const initialState = fromJS({
|
||||
// mediaTypes: ['news', 'blogs', 'socials', 'videos', 'forums', 'photo'],
|
||||
mediaTypes: allMediaTypes,
|
||||
// chosenMediaTypes: ['news', 'blogs', 'socials', 'videos', 'forums', 'photo'],
|
||||
chosenMediaTypes: [], // set only the allowed media types from restrictions initially
|
||||
searchLastDates: ['1d', '7d', '15d', '30d'], // 15d as 2W, to match with subscription
|
||||
searchIntervals: ['all', 'last', 'between'],
|
||||
chosenSearchDate: 'all',
|
||||
chosenSearchInterval: 'all',
|
||||
chosenSearchLastDate: '1d',
|
||||
chosenStartDate: '',
|
||||
chosenEndDate: '',
|
||||
isSearchByVisible: false,
|
||||
searchByTabs: [
|
||||
'emphasis', 'languages', 'locations', 'sources', 'sourceLists', 'extras'
|
||||
],
|
||||
chosenSearchByTab: 'emphasis',
|
||||
searchLanguages: ['af', 'sq', 'ar', 'bn', 'bs', 'bg', 'ca', 'zh', 'hr', 'cs',
|
||||
'da', 'nl', 'en', 'et', 'tl', 'fi', 'fr', 'de', 'el', 'he', 'hi', 'hu', 'is',
|
||||
'id', 'it', 'ja', 'ko', 'lv', 'lt', 'mk', 'ms', 'no', 'fa', 'pl', 'pt', 'ro',
|
||||
'ru', 'sr', 'sk', 'sl', 'es', 'sv', 'ta', 'th', 'tr', 'uk', 'ur', 'vi'],
|
||||
chosenLanguages: [],
|
||||
locations,
|
||||
chosenLocationsType: 'country',
|
||||
locationsToInclude: [],
|
||||
locationsToExclude: [],
|
||||
headlineIncluded: '',
|
||||
headlineExcluded: '',
|
||||
searchBySourcesQuery: '',
|
||||
searchBySources: [],
|
||||
selectedSearchBySources: [],
|
||||
searchBySourcesType: 'include',
|
||||
searchBySourceLists: [],
|
||||
searchBySourceListsAvailable: [],
|
||||
searchBySourceListsToInclude: [],
|
||||
searchBySourceListsToExclude: [],
|
||||
hasImages: false
|
||||
})
|
||||
|
||||
/*
|
||||
* Reducers
|
||||
* */
|
||||
export default handleActions({
|
||||
|
||||
[TOGGLE_MEDIA_TYPE]: (state, {payload}) => {
|
||||
const chosenTypes = state.get('chosenMediaTypes')
|
||||
|
||||
const newChosenTypes = payload.isChosen
|
||||
? chosenTypes.concat(payload.chosenType)
|
||||
: chosenTypes.filter((type) => {
|
||||
return payload.chosenType !== type
|
||||
})
|
||||
|
||||
return state.set('chosenMediaTypes', newChosenTypes)
|
||||
},
|
||||
|
||||
[TOGGLE_ALL_MEDIA_TYPES]: (state, {payload: isChosen}) => {
|
||||
const chosenTypes = isChosen ? state.get('mediaTypes').toJS() : []
|
||||
return state.set('chosenMediaTypes', chosenTypes)
|
||||
},
|
||||
|
||||
[SET_SEARCH_INTERVAL]: (state, {payload: newInterval}) => {
|
||||
return state.set('chosenSearchInterval', newInterval)
|
||||
},
|
||||
|
||||
[SET_SEARCH_LAST_DATE]: (state, {payload: newLastDate}) => {
|
||||
return state.set('chosenSearchLastDate', newLastDate)
|
||||
},
|
||||
|
||||
[SET_SEARCH_DATE]: (state, {payload: newDate}) => {
|
||||
return state.set('chosenSearchDate', newDate)
|
||||
},
|
||||
|
||||
[SET_START_DATE]: (state, {payload: newDate}) => {
|
||||
return state.set('chosenStartDate', newDate)
|
||||
},
|
||||
|
||||
[SET_END_DATE]: (state, {payload: newDate}) => {
|
||||
return state.set('chosenEndDate', newDate)
|
||||
},
|
||||
|
||||
[TOGGLE_SEARCH_BY]: (state, {payload}) => {
|
||||
const isSearchByVisible = !state.get('isSearchByVisible')
|
||||
return state.set('isSearchByVisible', isSearchByVisible)
|
||||
},
|
||||
|
||||
[CHOOSE_SEARCH_BY_TAB]: (state, {payload: tabName}) => {
|
||||
return state.set('chosenSearchByTab', tabName)
|
||||
},
|
||||
|
||||
[SET_HEADLINE_INCLUDED]: (state, {payload: headline}) => {
|
||||
return state.set('headlineIncluded', headline)
|
||||
},
|
||||
|
||||
[SET_HEADLINE_EXCLUDED]: (state, {payload: headline}) => {
|
||||
return state.set('headlineExcluded', headline)
|
||||
},
|
||||
|
||||
[TOGGLE_LANG]: (state, {payload}) => {
|
||||
const chosenLangs = state.get('chosenLanguages')
|
||||
|
||||
const newChosenLangs = payload.isChosen
|
||||
? chosenLangs.concat(payload.chosenLang)
|
||||
: chosenLangs.filter((lang) => {
|
||||
return payload.chosenLang !== lang
|
||||
})
|
||||
|
||||
return state.set('chosenLanguages', newChosenLangs)
|
||||
},
|
||||
|
||||
[TOGGLE_ALL_LANGS]: (state, {payload: isChosen}) => {
|
||||
const chosenLangs = isChosen ? state.get('searchLanguages').toJS() : []
|
||||
return state.set('chosenLanguages', chosenLangs)
|
||||
},
|
||||
|
||||
[CHANGE_LOCATIONS_TYPE]: (state, {payload: newLocationsType}) => {
|
||||
return state.set('chosenLocationsType', newLocationsType)
|
||||
},
|
||||
|
||||
[MOVE_LOCATION]: (state, { payload }) => {
|
||||
const listFrom = state.get(payload.from)
|
||||
const filteredList = listFrom.filter((loc) => {
|
||||
return loc.get('code') !== payload.loc.code
|
||||
})
|
||||
const listTo = state.get(payload.to).push(fromJS(payload.loc))
|
||||
|
||||
return state.merge({
|
||||
[payload.from]: filteredList,
|
||||
[payload.to]: listTo
|
||||
})
|
||||
},
|
||||
|
||||
[CLEAR_LOCATIONS]: (state) => {
|
||||
const locations = initialState.get('locations')
|
||||
const chosenLocationsType = initialState.get('chosenLocationsType')
|
||||
const locationsToInclude = initialState.get('locationsToInclude')
|
||||
const locationsToExclude = initialState.get('locationsToExclude')
|
||||
|
||||
return state.merge({
|
||||
locations,
|
||||
chosenLocationsType,
|
||||
locationsToInclude,
|
||||
locationsToExclude
|
||||
})
|
||||
},
|
||||
|
||||
[`${GET_SEARCH_BY_SOURCES} fulfilled`]: (state, { payload }) => {
|
||||
const response = payload.sources.data
|
||||
return state.set('searchBySources', response)
|
||||
},
|
||||
|
||||
[SET_SEARCH_BY_SOURCES_QUERY]: (state, {payload: query}) => {
|
||||
return state.set('searchBySourcesQuery', query)
|
||||
},
|
||||
|
||||
[ADD_SELECTED_SEARCH_BY_SOURCE]: (state, {payload: source}) => {
|
||||
const selectedSources = state.get('selectedSearchBySources')
|
||||
const isNew = !selectedSources.find(chosenSource => chosenSource.get('id') === source.id)
|
||||
const newSources = isNew ? selectedSources.push(fromJS(source)) : selectedSources
|
||||
|
||||
return state.set('selectedSearchBySources', newSources)
|
||||
},
|
||||
|
||||
[REMOVE_SELECTED_SEARCH_BY_SOURCE]: (state, {payload: sourceId}) => {
|
||||
const newSources = state.get('selectedSearchBySources').filter((sourceItem) => {
|
||||
return sourceItem.get('id') !== sourceId
|
||||
})
|
||||
return state.set('selectedSearchBySources', newSources)
|
||||
},
|
||||
|
||||
[CLEAR_SEARCH_BY_SOURCES]: (state) => {
|
||||
return state.merge({
|
||||
'selectedSearchBySources': fromJS([])
|
||||
})
|
||||
},
|
||||
|
||||
[INCLUDE_EXCLUDE_SEARCH_BY_SOURCES]: (state, {payload: sourceType}) => {
|
||||
return state.set('searchBySourcesType', sourceType)
|
||||
},
|
||||
|
||||
[`${GET_SEARCH_BY_SOURCE_LISTS} fulfilled`]: (state, { payload }) => {
|
||||
const includedListsIds = state.get('searchBySourceListsToInclude').map(list => list.get('id'))
|
||||
const excludedListsIds = state.get('searchBySourceListsToExclude').map(list => list.get('id'))
|
||||
const usedListsIds = includedListsIds.concat(excludedListsIds)
|
||||
|
||||
const allLists = fromJS(payload.data)
|
||||
const lists = allLists.filter(list => !usedListsIds.includes(list.get('id')))
|
||||
|
||||
return state.merge({
|
||||
searchBySourceListsAvailable: lists,
|
||||
searchBySourceLists: allLists
|
||||
})
|
||||
},
|
||||
|
||||
[MOVE_SOURCE_LIST]: (state, { payload }) => {
|
||||
const listFrom = fromJS(state.get(payload.from))
|
||||
const filteredList = listFrom.filter((list) => {
|
||||
return list.get('id') !== payload.list.id
|
||||
})
|
||||
const listTo = state.get(payload.to).push(fromJS(payload.list))
|
||||
|
||||
return state.merge({
|
||||
[payload.from]: filteredList,
|
||||
[payload.to]: listTo
|
||||
})
|
||||
},
|
||||
|
||||
[TOGGLE_INCLUDE_DUPLICATES]: (state, {payload}) => {
|
||||
const includeDuplicates = !state.get('includeDuplicates')
|
||||
return state.set('includeDuplicates', includeDuplicates)
|
||||
},
|
||||
|
||||
[TOGGLE_HAS_IMAGES]: (state, {payload}) => {
|
||||
const hasImages = !state.get('hasImages')
|
||||
return state.set('hasImages', hasImages)
|
||||
},
|
||||
|
||||
[RENEW_SEARCH_BY]: (state) => {
|
||||
|
||||
const {
|
||||
common: { auth }
|
||||
} = storeObj.getState().toJS();
|
||||
|
||||
let chosenMediaTypes = [];
|
||||
|
||||
if (
|
||||
auth &&
|
||||
auth.user &&
|
||||
auth.user.restrictions &&
|
||||
auth.user.restrictions.plans
|
||||
) {
|
||||
const planDetails = auth.user.restrictions.plans;
|
||||
chosenMediaTypes = allMediaTypes.filter((v) => planDetails[v]);
|
||||
/* if (auth.user.restrictions.plans.price === 0) {
|
||||
// TODO: remove following restrictions when duplication fixes
|
||||
const restrictedTemporary = ['news', 'blogs'];
|
||||
chosenMediaTypes = chosenMediaTypes.filter(
|
||||
(v) => !restrictedTemporary.includes(v)
|
||||
);
|
||||
} */
|
||||
}
|
||||
|
||||
return state.merge(initialState.toJS()).set('chosenMediaTypes', chosenMediaTypes)
|
||||
},
|
||||
|
||||
[SET_COMMON_FILTERS]: (state, {payload}) => {
|
||||
const { filters, sourceLists, sources } = payload
|
||||
|
||||
let result = initialState
|
||||
.merge({
|
||||
chosenSearchByTab: state.get('chosenSearchByTab'),
|
||||
isSearchByVisible: state.get('isSearchByVisible'),
|
||||
searchBySources: state.get('searchBySources'),
|
||||
searchBySourceLists: state.get('searchBySourceLists')
|
||||
})
|
||||
.toJS()
|
||||
|
||||
if (filters.headline) {
|
||||
result.headlineIncluded = filters.headline.include || ''
|
||||
result.headlineExcluded = filters.headline.exclude || ''
|
||||
}
|
||||
|
||||
const { source, domain } = filters.publisher || {}
|
||||
if (source || domain) {
|
||||
const medias = (source || []).concat(domain || []);
|
||||
result.chosenMediaTypes = medias.map((v) => v.split('.')[0]);
|
||||
}
|
||||
|
||||
if (filters.source && sources.length > 0) {
|
||||
result.searchBySourcesType = filters.source.type
|
||||
result.selectedSearchBySources = sources
|
||||
}
|
||||
|
||||
let usedSourceLists = []
|
||||
if (filters.sourceList) {
|
||||
if (filters.sourceList.include) {
|
||||
result.searchBySourceListsToInclude = sourceLists.filter(source => {
|
||||
return filters.sourceList.include.includes(source.id)
|
||||
})
|
||||
usedSourceLists = usedSourceLists.concat(filters.sourceList.include)
|
||||
}
|
||||
if (filters.sourceList.exclude) {
|
||||
result.searchBySourceListsToExclude = sourceLists.filter(source => {
|
||||
return filters.sourceList.exclude.includes(source.id)
|
||||
})
|
||||
usedSourceLists = usedSourceLists.concat(filters.sourceList.exclude)
|
||||
}
|
||||
}
|
||||
|
||||
result.searchBySourceListsAvailable = result.searchBySourceLists.filter(source => {
|
||||
return !usedSourceLists.includes(source.id)
|
||||
})
|
||||
|
||||
if (filters.language) {
|
||||
result.chosenLanguages = filters.language
|
||||
}
|
||||
|
||||
if (filters.country || filters.state) {
|
||||
let locationsToInclude = []
|
||||
let locationsToExclude = []
|
||||
let locationsMain = []
|
||||
locations.forEach(location => {
|
||||
const code = location.code
|
||||
|
||||
if (location.type === 'country') {
|
||||
if (filters.country && filters.country.include && filters.country.include.includes(code)) {
|
||||
locationsToInclude.push(location)
|
||||
}
|
||||
else if (filters.country && filters.country.exclude && filters.country.exclude.includes(code)) {
|
||||
locationsToExclude.push(location)
|
||||
}
|
||||
else {
|
||||
locationsMain.push(location)
|
||||
}
|
||||
}
|
||||
|
||||
if (location.type === 'state') {
|
||||
if (filters.state && filters.state.include && filters.state.include.includes(code)) {
|
||||
locationsToInclude.push(location)
|
||||
}
|
||||
else if (filters.state && filters.state.exclude && filters.state.exclude.includes(code)) {
|
||||
locationsToExclude.push(location)
|
||||
}
|
||||
else {
|
||||
locationsMain.push(location)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
result.locationsToInclude = locationsToInclude
|
||||
result.locationsToExclude = locationsToExclude
|
||||
result.locations = locationsMain
|
||||
}
|
||||
|
||||
if (filters.date) {
|
||||
let dateResult = {}
|
||||
if (filters.date.type === 'all') {
|
||||
dateResult = {
|
||||
chosenSearchDate: 'all',
|
||||
chosenSearchInterval: 'all',
|
||||
chosenSearchLastDate: '',
|
||||
chosenStartDate: '',
|
||||
chosenEndDate: ''
|
||||
}
|
||||
}
|
||||
else if (filters.date.type === 'last') {
|
||||
dateResult = {
|
||||
chosenSearchDate: filters.date.days + 'd',
|
||||
chosenSearchInterval: 'last',
|
||||
chosenSearchLastDate: filters.date.days + 'd',
|
||||
chosenStartDate: '',
|
||||
chosenEndDate: ''
|
||||
}
|
||||
}
|
||||
else if (filters.date.type === 'between') {
|
||||
dateResult = {
|
||||
chosenSearchDate: `${filters.date.start} - ${filters.date.end}`,
|
||||
chosenSearchInterval: 'between',
|
||||
chosenSearchLastDate: '',
|
||||
chosenStartDate: filters.date.start,
|
||||
chosenEndDate: filters.date.end
|
||||
}
|
||||
}
|
||||
result = Object.assign(result, dateResult)
|
||||
}
|
||||
if (filters.duplicates) {
|
||||
result.includeDuplicates = filters.duplicates
|
||||
}
|
||||
if (filters.hasImage) {
|
||||
result.hasImages = filters.hasImage
|
||||
}
|
||||
|
||||
return state.merge(result)
|
||||
}
|
||||
|
||||
}, initialState)
|
||||
@@ -0,0 +1,232 @@
|
||||
import {ReduxModule} from '../../../abstract/reduxModule'
|
||||
|
||||
class ThemeForm extends ReduxModule {
|
||||
|
||||
getNamespace () {
|
||||
return '[Theme form]'
|
||||
}
|
||||
|
||||
getInitialState () {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
export default ThemeForm
|
||||
|
||||
/*export class ThemeForm extends abstractModule {
|
||||
constructor () {
|
||||
super();
|
||||
this.initNamespace('THEMES_');
|
||||
}
|
||||
|
||||
initInitialState () {
|
||||
const rgbaString = 'rgba(0, 0, 0, 1)';
|
||||
const string = '';
|
||||
const int = 0;
|
||||
const boolean = false;
|
||||
const languageCode = 'en';
|
||||
const File = new File;
|
||||
|
||||
const FontOptions = {
|
||||
family: string,
|
||||
size: int,
|
||||
style: {
|
||||
bold: boolean,
|
||||
italic: boolean,
|
||||
underline: boolean
|
||||
}
|
||||
};
|
||||
|
||||
// sample of structure for backend
|
||||
const BackendThemeOptions = {
|
||||
type: 'enhanced' || 'plain',
|
||||
summary: string,
|
||||
conclusion: string,
|
||||
header: {
|
||||
imageUrl: string,
|
||||
logoLink: string || '',
|
||||
title: 'Newsletter' // only for enhanced
|
||||
},
|
||||
fonts: {
|
||||
header: FontOptions,
|
||||
tableOfContents: FontOptions,
|
||||
feeTitle: FontOptions,
|
||||
articleHeadline: FontOptions,
|
||||
source: FontOptions,
|
||||
author: FontOptions,
|
||||
date: FontOptions,
|
||||
articleContent: FontOptions
|
||||
},
|
||||
content: {
|
||||
highlightKeywords: {
|
||||
bold: boolean,
|
||||
color: rgbaString
|
||||
},
|
||||
showInfo: {
|
||||
sourceCountry: boolean,
|
||||
articleSentiment: boolean,
|
||||
articleCount: boolean,
|
||||
images: boolean,
|
||||
sharingOptions: boolean,
|
||||
sectionDivider: boolean,
|
||||
userComments: 'no' || 'with_author_date' || 'without_author_date',
|
||||
tableOfContents: {
|
||||
visible: boolean,
|
||||
headline: 'no' || 'headline' || 'headline_source_date' || 'source_headline_date'
|
||||
}
|
||||
},
|
||||
language: languageCode,
|
||||
extract: 'start' || 'context' || 'no'
|
||||
},
|
||||
colors: {
|
||||
background: {
|
||||
header: rgbaString,
|
||||
emailBody: rgbaString,
|
||||
accent: rgbaString
|
||||
},
|
||||
text: {
|
||||
header: rgbaString,
|
||||
articleHeadline: rgbaString,
|
||||
articleContent: rgbaString,
|
||||
author: rgbaString,
|
||||
publishDate: rgbaString,
|
||||
source: rgbaString
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.initialState = fromJS({
|
||||
name: string,
|
||||
template: string,
|
||||
options: {
|
||||
type: 'enhanced' || 'plain',
|
||||
summary: {
|
||||
value: string,
|
||||
isEdit: boolean
|
||||
},
|
||||
conclusion: {
|
||||
value: string,
|
||||
isEdit: boolean
|
||||
},
|
||||
header: {
|
||||
imageUrl: {
|
||||
file: File,
|
||||
url: string
|
||||
},
|
||||
logoLink: string,
|
||||
title: {
|
||||
value: string,
|
||||
isEdit: boolean
|
||||
}
|
||||
},
|
||||
fonts: {
|
||||
header: FontOptions,
|
||||
tableOfContents: FontOptions,
|
||||
feeTitle: FontOptions,
|
||||
articleHeadline: FontOptions,
|
||||
source: FontOptions,
|
||||
author: FontOptions,
|
||||
date: FontOptions,
|
||||
articleContent: FontOptions
|
||||
},
|
||||
content: {
|
||||
language: languageCode,
|
||||
extract: {
|
||||
value: 'start',
|
||||
entities: [{
|
||||
value: 'start',
|
||||
label: 'Start of text extract'
|
||||
}, {
|
||||
value: 'context',
|
||||
label: 'Contextual extract'
|
||||
}, {
|
||||
value: 'no',
|
||||
label: 'No article extract'
|
||||
}]
|
||||
},
|
||||
highlightKeywords: {
|
||||
bold: boolean,
|
||||
color: rgbaString,
|
||||
colorPresets: [rgbaString]
|
||||
},
|
||||
showInfo: {
|
||||
sourceCountry: boolean,
|
||||
articleSentiment: boolean,
|
||||
articleCount: boolean,
|
||||
images: boolean,
|
||||
sharingOptions: boolean,
|
||||
sectionDivider: boolean,
|
||||
userComments: {
|
||||
value: 'no',
|
||||
entities: [{
|
||||
value: 'no',
|
||||
label: 'No User Comments'
|
||||
}, {
|
||||
value: 'with_author_date',
|
||||
label: 'User Comments with Author/Date'
|
||||
}, {
|
||||
value: 'without_author_date',
|
||||
label: 'User Comments without Author/Date'
|
||||
}]
|
||||
},
|
||||
tableOfContents: {
|
||||
types: {
|
||||
value: 'simple',
|
||||
entities: [{
|
||||
value: 'simple',
|
||||
label: 'Table of contents'
|
||||
}, {
|
||||
value: 'headlines',
|
||||
label: 'Table of contents with headlines'
|
||||
}, {
|
||||
value: 'no',
|
||||
label: 'No table of contents'
|
||||
}]
|
||||
},
|
||||
headlines: {
|
||||
value: null,
|
||||
entities: [{
|
||||
value: 'headline',
|
||||
label: 'Headline only'
|
||||
}, {
|
||||
value: 'headline_source_date',
|
||||
label: 'Headline | Source | Date'
|
||||
}, {
|
||||
value: 'source_headline_date',
|
||||
label: 'Source | Headline | Date'
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
colors: {
|
||||
background: {
|
||||
header: rgbaString,
|
||||
emailBody: rgbaString,
|
||||
accent: rgbaString
|
||||
},
|
||||
text: {
|
||||
header: rgbaString,
|
||||
articleHeadline: rgbaString,
|
||||
articleContent: rgbaString,
|
||||
author: rgbaString,
|
||||
publishDate: rgbaString,
|
||||
source: rgbaString
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
initActions () {
|
||||
this.actions = {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const themeForm = new ThemeForm();
|
||||
themeForm.init();
|
||||
|
||||
export default themeForm.reducers;
|
||||
*/
|
||||
@@ -0,0 +1,49 @@
|
||||
import * as api from '../../../../../api/themesApi'
|
||||
import ReduxModule from '../../../abstract/reduxModule'
|
||||
|
||||
const GET_DEFAULT_THEME = 'Get default theme'
|
||||
|
||||
export class Themes extends ReduxModule {
|
||||
|
||||
getNamespace () {
|
||||
return '[Themes]'
|
||||
}
|
||||
|
||||
getDefaultTheme = ({token, fulfilled}) => {
|
||||
return api
|
||||
.getDefaultItem(token)
|
||||
.then((data) => {
|
||||
fulfilled(data)
|
||||
return data
|
||||
})
|
||||
};
|
||||
|
||||
defineActions () {
|
||||
const getDefaultTheme = this.thunkAction(GET_DEFAULT_THEME, this.getDefaultTheme)
|
||||
return {
|
||||
getDefaultTheme
|
||||
}
|
||||
}
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
isPending: false,
|
||||
themes: [],
|
||||
defaultTheme: null,
|
||||
activeId: ''
|
||||
}
|
||||
}
|
||||
|
||||
defineReducers () {
|
||||
return {
|
||||
[`${GET_DEFAULT_THEME} fulfilled`]: this.setReducer('defaultTheme')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const themes = new Themes()
|
||||
themes.init()
|
||||
export default themes
|
||||
|
||||
export const getDefaultTheme = themes.actions.getDefaultTheme
|
||||
@@ -0,0 +1,77 @@
|
||||
import {handleActions, createAction} from 'redux-actions'
|
||||
import {fromJS} from 'immutable'
|
||||
import {thunkAction} from '../../../utils/common'
|
||||
import * as api from '../../../../api/feedsApi'
|
||||
import {getSidebarCategories} from '../sidebar'
|
||||
import { getRestrictions } from '../../common/auth'
|
||||
|
||||
/** CONSTANTS **/
|
||||
const NS = '[Export]'
|
||||
const LOAD_EXPORTED_FEEDS = `${NS} Load exported feeds`
|
||||
const SHOW_EXPORT_POPUP = `${NS} Show export popup`
|
||||
const HIDE_EXPORT_POPUP = `${NS} Hide export popup`
|
||||
const UNEXPORT_FEED = `${NS} Unexport feed`
|
||||
|
||||
/** ACTIONS **/
|
||||
|
||||
const loadExportedFeeds = thunkAction(LOAD_EXPORTED_FEEDS, ({token, fulfilled}) => {
|
||||
return api
|
||||
.loadExportedFeeds(token)
|
||||
.then((data) => {
|
||||
fulfilled(data)
|
||||
})
|
||||
}, true)
|
||||
|
||||
const showExportPopup = createAction(SHOW_EXPORT_POPUP, (feed, exportFormat) => ({feed, exportFormat}))
|
||||
const hideExportPopup = createAction(HIDE_EXPORT_POPUP)
|
||||
|
||||
const unexportFeed = thunkAction(UNEXPORT_FEED, (feedId, {token, fulfilled, dispatch}) => {
|
||||
return api
|
||||
.toggleExportFeed(token, {export: false}, feedId)
|
||||
.then(() => {
|
||||
fulfilled()
|
||||
dispatch(getSidebarCategories())
|
||||
dispatch(getRestrictions())
|
||||
dispatch(loadExportedFeeds())
|
||||
})
|
||||
})
|
||||
|
||||
export const actions = {
|
||||
loadExportedFeeds,
|
||||
showExportPopup,
|
||||
hideExportPopup,
|
||||
unexportFeed
|
||||
}
|
||||
|
||||
//**** STATE ****//
|
||||
export const initialState = fromJS({
|
||||
isLoading: false,
|
||||
tableData: [],
|
||||
popupVisible: false,
|
||||
selectedFeed: null,
|
||||
exportFormat: ''
|
||||
})
|
||||
|
||||
export default handleActions({
|
||||
|
||||
[`${LOAD_EXPORTED_FEEDS} pending`]: (state, {payload: {isPending}}) => {
|
||||
return state.set('isLoading', isPending)
|
||||
},
|
||||
|
||||
[`${LOAD_EXPORTED_FEEDS} fulfilled`]: (state, {payload: data}) => {
|
||||
return state.set('tableData', data)
|
||||
},
|
||||
|
||||
[SHOW_EXPORT_POPUP]: (state, {payload: {feed, exportFormat}}) => {
|
||||
return state.merge({
|
||||
selectedFeed: feed,
|
||||
popupVisible: true,
|
||||
exportFormat
|
||||
})
|
||||
},
|
||||
|
||||
[HIDE_EXPORT_POPUP]: (state) => {
|
||||
return state.set('popupVisible', false)
|
||||
}
|
||||
|
||||
}, initialState)
|
||||
@@ -0,0 +1,117 @@
|
||||
import { getCurrentTimezone } from '../../../../../common/Timezones'
|
||||
import { createItem, updateItem } from '../../../../../api/notificationsApi'
|
||||
import {NotificationForm, THEME_TYPES} from './notificationForm'
|
||||
import {addAlert} from '../../../common/alerts'
|
||||
import {getRestrictions} from '../../../common/auth'
|
||||
import {switchShareSubScreen, NOTIFICATION_SUBSCREENS} from '../tabs'
|
||||
|
||||
export const EXTRAS = {
|
||||
CONTEXTUAL: 'context',
|
||||
START: 'start',
|
||||
NO: 'no'
|
||||
}
|
||||
|
||||
export class AlertForm extends NotificationForm {
|
||||
|
||||
getNamespace () {
|
||||
return '[Alert Form]'
|
||||
}
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
name: 'New Alert',
|
||||
recipients: [],
|
||||
subject: '',
|
||||
automatedSubject: false,
|
||||
published: false,
|
||||
allowUnsubscribe: false,
|
||||
unsubscribeNotification: false,
|
||||
sources: [],
|
||||
content: {
|
||||
extract: EXTRAS.CONTEXTUAL,
|
||||
highlightKeywords: {
|
||||
highlight: false
|
||||
},
|
||||
showInfo: {
|
||||
sourceCountry: false,
|
||||
userComments: 'no'
|
||||
}
|
||||
},
|
||||
themeType: THEME_TYPES.PLAIN,
|
||||
sendWhenEmpty: false,
|
||||
timezone: getCurrentTimezone(),
|
||||
isEnabledTimezone: false,
|
||||
isDefaultTimezone: true,
|
||||
sendUntil: '',
|
||||
scheduling: {
|
||||
constants: {
|
||||
type: ['daily', 'weekly', 'monthly'],
|
||||
time: ['15m', '30m', '1h', '2h', '3h', '4h', '6h', '12h', 'once'],
|
||||
days: ['all', 'weekdays', 'weekends'],
|
||||
period: ['every', 'first', 'second', 'third', 'fourth', 'last'],
|
||||
day: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'],
|
||||
monthDay: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 'Last'],
|
||||
hour: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23],
|
||||
minute: [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55]
|
||||
},
|
||||
newTime: {
|
||||
type: 'daily',
|
||||
time: '15m',
|
||||
days: 'all',
|
||||
period: 'every',
|
||||
monthDay: 1,
|
||||
day: 'monday',
|
||||
hour: 13,
|
||||
minute: 0
|
||||
},
|
||||
times: []
|
||||
},
|
||||
showSaveAsPopup: false,
|
||||
sendHistory: {
|
||||
isOpen: false,
|
||||
isPending: false,
|
||||
isLoadingCompleted: false,
|
||||
page: 0,
|
||||
limit: 5,
|
||||
totalCount: 0,
|
||||
entities: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
serialize (data, theme) {
|
||||
let serialized = super.serialize(data, theme)
|
||||
return {
|
||||
...serialized,
|
||||
notificationType: 'alert'
|
||||
}
|
||||
}
|
||||
|
||||
saveAlert = (isEdit, {dispatch, getState, token}) => {
|
||||
const data = getState().getIn(['appState', 'share', 'forms', 'alert'])
|
||||
const id = data.get('id')
|
||||
const theme = getState().getIn(['appState', 'share', 'themes', 'defaultTheme'])
|
||||
const alertData = this.serialize(data, theme)
|
||||
const promise = isEdit ? updateItem(token, alertData, id) : createItem(token, alertData)
|
||||
return promise
|
||||
.then((data) => {
|
||||
dispatch(getRestrictions())
|
||||
dispatch(addAlert({type: 'notice', transKey: 'alertSaved'}))
|
||||
dispatch(switchShareSubScreen('notifications', NOTIFICATION_SUBSCREENS.TABLES))
|
||||
})
|
||||
};
|
||||
|
||||
defineActions () {
|
||||
const actions = super.defineActions()
|
||||
const saveAlert = this.thunkAction('SAVE_ALERT', this.saveAlert)
|
||||
return {
|
||||
...actions,
|
||||
saveAlert
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const instance = new AlertForm()
|
||||
instance.init()
|
||||
export default instance
|
||||
@@ -0,0 +1,94 @@
|
||||
import {ReceiverForm} from './receiverForm'
|
||||
import {
|
||||
GROUP_FORM_TABLES, switchShareSubScreen, switchShareTable, RECEIVER_TABLES, RECEIVER_SUBSCREENS
|
||||
} from '../tabs'
|
||||
import {fromJS} from 'immutable'
|
||||
import * as api from '../../../../../api/groupsApi'
|
||||
import {addAlert} from '../../../common/alerts'
|
||||
|
||||
const SAVE_GROUP = 'Save group'
|
||||
|
||||
export class GroupForm extends ReceiverForm {
|
||||
|
||||
constructor () {
|
||||
super(api)
|
||||
}
|
||||
|
||||
getNamespace () {
|
||||
return '[Group Form]'
|
||||
}
|
||||
|
||||
serialize (data) {
|
||||
return {
|
||||
name: data.name,
|
||||
description: data.description,
|
||||
active: data.active,
|
||||
recipients: data.recipients,
|
||||
notifications: data.subscriptions
|
||||
}
|
||||
};
|
||||
|
||||
normalize (data) {
|
||||
let newState = this.getInitialState()
|
||||
newState = {
|
||||
...newState,
|
||||
id: data.id,
|
||||
name: data.name,
|
||||
description: data.description || '',
|
||||
active: data.active,
|
||||
recipients: data.recipients,
|
||||
subscriptions: data.subscriptions.ids
|
||||
}
|
||||
return fromJS(newState)
|
||||
};
|
||||
|
||||
saveGroup = ({dispatch, fulfilled, getState, token}) => {
|
||||
const data = getState().getIn(['appState', 'share', 'forms', 'group']).toJS()
|
||||
const id = data.id
|
||||
|
||||
if (data.name) {
|
||||
const payload = this.serialize(data)
|
||||
const apiRequest = id ? api.updateItem(token, payload, id) : api.createItem(token, payload)
|
||||
return apiRequest.then(() => {
|
||||
fulfilled()
|
||||
dispatch(addAlert({type: 'notice', transKey: 'groupSaved'}))
|
||||
dispatch(switchShareSubScreen('recipients', RECEIVER_SUBSCREENS.TABLES))
|
||||
dispatch(switchShareTable('recipients', RECEIVER_TABLES.GROUPS))
|
||||
})
|
||||
|
||||
} else {
|
||||
dispatch(addAlert({type: 'error', transKey: 'groupNameEmpty'}))
|
||||
}
|
||||
};
|
||||
|
||||
defineActions () {
|
||||
const saveReceiver = this.thunkAction(SAVE_GROUP, this.saveGroup, true)
|
||||
|
||||
return {
|
||||
...super.defineActions(),
|
||||
saveReceiver,
|
||||
deleteReceiver: this.deleteGroup
|
||||
}
|
||||
}
|
||||
|
||||
getTableTabs () {
|
||||
return [GROUP_FORM_TABLES.RECIPIENTS, GROUP_FORM_TABLES.SUBSCRIPTIONS, GROUP_FORM_TABLES.EMAIL_HISTORY]
|
||||
}
|
||||
|
||||
getInitialState () {
|
||||
const state = super.getInitialState()
|
||||
return {
|
||||
name: '',
|
||||
description: '',
|
||||
creationDate: '',
|
||||
recipients: [],
|
||||
subscriptions: [],
|
||||
...state
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const instance = new GroupForm()
|
||||
instance.init()
|
||||
export default instance
|
||||
@@ -0,0 +1,18 @@
|
||||
import {NotificationForm} from './notificationForm'
|
||||
|
||||
export class NewsletterForm extends NotificationForm {
|
||||
|
||||
getNamespace () {
|
||||
return '[Newsletter Form]'
|
||||
}
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const instance = new NewsletterForm()
|
||||
instance.init()
|
||||
export default instance
|
||||
@@ -0,0 +1,313 @@
|
||||
import {ReduxModule} from '../../../abstract/reduxModule'
|
||||
import {fromJS} from 'immutable'
|
||||
import { getItems as getReceiversApi } from '../../../../../api/receiversApi'
|
||||
import { getHistory as getHistoryApi } from '../../../../../api/notificationsApi'
|
||||
import {tokenInject} from '../../../../utils/common'
|
||||
import {addAlert} from '../../../common/alerts'
|
||||
|
||||
const ADD_SOURCE = 'ADD_SOURCE'
|
||||
const REMOVE_SOURCE = 'REMOVE_SOURCE'
|
||||
const MOVE_SOURCE = 'MOVE_SOURCE'
|
||||
const ADD_SCHEDULE = 'ADD_SCHEDULE'
|
||||
const REMOVE_SCHEDULE = 'REMOVE_SCHEDULE'
|
||||
const FILL_FORM = 'FILL_FORM'
|
||||
const HISTORY = 'HISTORY'
|
||||
const CHANGE_SCHEDULE = 'CHANGE_SCHEDULE'
|
||||
|
||||
export const THEME_TYPES = {
|
||||
PLAIN: 'plain',
|
||||
ENHANCED: 'enhanced'
|
||||
}
|
||||
|
||||
export class NotificationForm extends ReduxModule {
|
||||
|
||||
normalize (data, theme) {
|
||||
// console.log('notificationForm::normalize, input=', data)
|
||||
let newState = this.getInitialState()
|
||||
const automatic = data.automatic
|
||||
const notificationType = data.type
|
||||
const themeType = data.themeType
|
||||
const recipients = data.recipients
|
||||
const themeDiff = themeType === 'plain' ? data.plainDiff : data.enhancedDiff
|
||||
const immTheme = fromJS(theme[themeType])
|
||||
|
||||
const fieldsToDelete = ['recipients', 'automatic', 'plainDiff', 'enhancedDiff', 'owner', 'type']
|
||||
fieldsToDelete.forEach(field => delete data[field])
|
||||
|
||||
newState = {
|
||||
...newState,
|
||||
...data,
|
||||
sendUntil: data.sendUntil || '',
|
||||
notificationType
|
||||
}
|
||||
|
||||
newState.scheduling.times = automatic.map(time => {
|
||||
if (time.type === 'monthly') {
|
||||
time.monthDay = (time.day === 'last') ? 'Last' : parseInt(time.day)
|
||||
}
|
||||
return time
|
||||
})
|
||||
|
||||
newState.recipients = recipients.map(r => ({label: r.name, value: r.id}))
|
||||
|
||||
newState = fromJS(newState)
|
||||
|
||||
const paths = ['content:extract', 'content:highlightKeywords:highlight', 'content:showInfo:sourceCountry', 'content:showInfo:userComments']
|
||||
paths.forEach(path => {
|
||||
const immPath = path.split(':')
|
||||
const value = (themeDiff[path]) ? themeDiff[path] : immTheme.getIn(immPath)
|
||||
newState = newState.setIn(immPath, value)
|
||||
})
|
||||
|
||||
// console.log('notificationForm::normalize, output=', newState.toJS())
|
||||
return newState
|
||||
}
|
||||
|
||||
_serializeScheduleItem (time) {
|
||||
switch (time.type) {
|
||||
case 'daily':
|
||||
delete time.period
|
||||
delete time.day
|
||||
delete time.hour
|
||||
delete time.minute
|
||||
break
|
||||
case 'weekly':
|
||||
delete time.time
|
||||
delete time.days
|
||||
break
|
||||
case 'monthly':
|
||||
delete time.period
|
||||
delete time.time
|
||||
delete time.days
|
||||
time.day = (time.monthDay === 'Last') ? 'last' : time.monthDay.toString()
|
||||
break
|
||||
}
|
||||
delete time.monthDay
|
||||
|
||||
return time
|
||||
}
|
||||
|
||||
serialize (state, theme) {
|
||||
const data = state.toJS()
|
||||
console.log('notificationForm:serialize, input=', data)
|
||||
const scheduling = data.scheduling
|
||||
const themeType = data.themeType
|
||||
|
||||
const fieldsToDelete = ['scheduling', 'showSaveAsPopup', 'id', 'content', 'isEnabledTimezone', 'isDefaultTimezone', 'sendHistory', 'active']
|
||||
fieldsToDelete.forEach(field => delete data[field])
|
||||
|
||||
const automatic = scheduling.times.map(this._serializeScheduleItem)
|
||||
|
||||
const sources = data.sources.map(source => {
|
||||
return {
|
||||
type: source.type,
|
||||
id: source.id
|
||||
}
|
||||
})
|
||||
|
||||
const recipients = data.recipients.map(recipient => recipient.value)
|
||||
|
||||
const themeDiff = {}
|
||||
const paths = ['content:extract', 'content:highlightKeywords:highlight', 'content:showInfo:sourceCountry', 'content:showInfo:userComments']
|
||||
const immTheme = fromJS(theme[themeType])
|
||||
paths.forEach(path => {
|
||||
const immPath = path.split(':')
|
||||
const themeValue = immTheme.getIn(immPath)
|
||||
const stateValue = state.getIn(immPath)
|
||||
if (themeValue !== stateValue) {
|
||||
themeDiff[path] = stateValue
|
||||
}
|
||||
})
|
||||
const enhancedDiff = themeType === 'enhanced' ? themeDiff : {}
|
||||
const plainDiff = themeType === 'plain' ? themeDiff : {}
|
||||
|
||||
const alertData = {
|
||||
...data,
|
||||
name: data.name || 'New Alert',
|
||||
theme: theme.id,
|
||||
automatic,
|
||||
recipients,
|
||||
sources,
|
||||
enhancedDiff,
|
||||
plainDiff
|
||||
}
|
||||
console.log('notificationForm:serialize, output=', alertData)
|
||||
return alertData
|
||||
}
|
||||
|
||||
getHistory (notificationId, page, limit, {fulfilled, token}) {
|
||||
return getHistoryApi(token, {page, limit}, notificationId)
|
||||
.then((historyData) => {
|
||||
fulfilled(historyData)
|
||||
})
|
||||
}
|
||||
|
||||
getRecipients (filter) {
|
||||
return tokenInject((dispatch, getState, token) => {
|
||||
return getReceiversApi(token, {filter})
|
||||
.then((data) => {
|
||||
const options = data.map(recipient => {
|
||||
const label = (recipient.type === 'recipient')
|
||||
? `${recipient.name} <${recipient.email}>`
|
||||
: recipient.name
|
||||
return {value: recipient.id, label}
|
||||
})
|
||||
return {options}
|
||||
})
|
||||
.catch((errors) => {
|
||||
dispatch(addAlert(errors))
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
defineActions () {
|
||||
const changeName = this.set('NAME', 'name')
|
||||
const changeRecipients = this.set('RECIPIENTS', 'recipients')
|
||||
const changeSubject = this.set('SUBJECT', 'subject')
|
||||
const changeAutoSubject = this.set('AUTO_SUBJECT', 'automatedSubject')
|
||||
const changePublished = this.set('PUBLISHED', 'published')
|
||||
const changeAllowUnsubscribe = this.set('ALLOW_UNSUBSCRIBE', 'allowUnsubscribe')
|
||||
const changeUnsubscribeNotification = this.set('UNSUBSCRIBE_NOTIFICATION', 'unsubscribeNotification')
|
||||
const changeExtras = this.setIn('EXTRAS', ['content', 'extract'])
|
||||
const changeHighlightKeywords = this.setIn('HIGHLIGHT_KEYWORDS', ['content', 'highlightKeywords', 'highlight'])
|
||||
const changeShowSourceCountry = this.setIn('SHOW_SOURCE_COUNTRY', ['content', 'showInfo', 'sourceCountry'])
|
||||
const changeShowUserComments = this.setIn('SHOW_USER_COMMENTS', ['content', 'showInfo', 'userComments'])
|
||||
const changeThemeType = this.set('THEME_TYPE', 'themeType')
|
||||
const changeSendWhenEmpty = this.set('SEND_WHEN_EMPTY', 'sendWhenEmpty')
|
||||
const changeTimezone = this.set('TIMEZONE', 'timezone')
|
||||
const changeSendUntil = this.set('SEND_UNTIL', 'sendUntil')
|
||||
const toggleTimezone = this.toggle('TOGGLE_TIMEZONE', 'isEnabledTimezone')
|
||||
const toggleSaveAsPopup = this.toggle('TOGGLE_SAVE_AS_POPUP', 'showSaveAsPopup')
|
||||
const toggleHistory = this.toggleIn('TOGGLE_HISTORY', ['sendHistory', 'isOpen'])
|
||||
// TODO reset options for every type
|
||||
const changeScheduleType = this.setIn('SCHEDULE_TYPE', ['scheduling', 'newTime', 'type'])
|
||||
const changeNewSchedule = this.mergeIn('CHANGE_NEW_SCHEDULE', ['scheduling', 'newTime'])
|
||||
const clearForm = this.resetToInitialState('CLEAR')
|
||||
|
||||
const addSource = this.createAction(ADD_SOURCE, source => source)
|
||||
const removeSource = this.createAction(REMOVE_SOURCE, sourceId => sourceId)
|
||||
const moveSource = this.createAction(MOVE_SOURCE, (sourceId, isUp) => ({sourceId, isUp}))
|
||||
const addSchedule = this.createAction(ADD_SCHEDULE)
|
||||
const removeSchedule = this.createAction(REMOVE_SCHEDULE, id => id)
|
||||
const fillForm = this.createAction(FILL_FORM, (item, theme) => ({item, theme}))
|
||||
const changeExistingSchedule = this.createAction('CHANGE_SCHEDULE', (item, id) => ({item, id}))
|
||||
|
||||
this.setPending = this.set('LOADING', 'isLoading')
|
||||
|
||||
// const historyPending = this.setIn(`${HISTORY} pending`, ['sendHistory', 'isPending'])
|
||||
const getHistory = this.thunkAction(HISTORY, this.getHistory, true)
|
||||
|
||||
return {
|
||||
changeName,
|
||||
changeRecipients,
|
||||
changeSubject,
|
||||
changeAutoSubject,
|
||||
changeAllowUnsubscribe,
|
||||
changeUnsubscribeNotification,
|
||||
changeExtras,
|
||||
changeHighlightKeywords,
|
||||
changeShowSourceCountry,
|
||||
changeShowUserComments,
|
||||
changeThemeType,
|
||||
changeSendWhenEmpty,
|
||||
changePublished,
|
||||
addSource,
|
||||
removeSource,
|
||||
addSchedule,
|
||||
removeSchedule,
|
||||
moveSource,
|
||||
changeTimezone,
|
||||
toggleTimezone,
|
||||
changeScheduleType,
|
||||
changeNewSchedule,
|
||||
changeExistingSchedule,
|
||||
changeSendUntil,
|
||||
fillForm,
|
||||
clearForm,
|
||||
getRecipients: this.getRecipients,
|
||||
toggleSaveAsPopup,
|
||||
toggleHistory,
|
||||
getHistory
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
defineReducers () {
|
||||
|
||||
return {
|
||||
[ADD_SOURCE]: (state, {payload: source}) => {
|
||||
const sources = state.get('sources').push(fromJS(source))
|
||||
return state.set('sources', sources)
|
||||
},
|
||||
|
||||
[REMOVE_SOURCE]: (state, {payload: sourceId}) => {
|
||||
const sources = state.get('sources').filter(source => source.get('id') !== sourceId)
|
||||
return state.set('sources', sources)
|
||||
},
|
||||
|
||||
[MOVE_SOURCE]: (state, {payload}) => {
|
||||
const { sourceId, isUp } = payload
|
||||
let sources = state.get('sources')
|
||||
|
||||
const fromIndex = sources.findIndex(source => source.get('id') === sourceId)
|
||||
const toIndex = (isUp) ? fromIndex - 1 : fromIndex + 1
|
||||
|
||||
const isUpFail = isUp && (toIndex < 0)
|
||||
const isDownFail = !isUp && (toIndex >= sources.size)
|
||||
if (isUpFail || isDownFail) {
|
||||
return state
|
||||
}
|
||||
|
||||
const source = sources.get(fromIndex)
|
||||
sources = sources
|
||||
.splice(fromIndex, 1)
|
||||
.splice(toIndex, 0, source)
|
||||
|
||||
return state.set('sources', sources)
|
||||
},
|
||||
|
||||
[ADD_SCHEDULE]: (state) => {
|
||||
const path = ['scheduling', 'times']
|
||||
const item = state.getIn(['scheduling', 'newTime'])
|
||||
const items = state.getIn(path).push(item)
|
||||
return state.setIn(path, items)
|
||||
},
|
||||
|
||||
[REMOVE_SCHEDULE]: (state, {payload: id}) => {
|
||||
const path = ['scheduling', 'times']
|
||||
const items = state.getIn(path).filter((item, index) => index !== id)
|
||||
return state.setIn(path, items)
|
||||
},
|
||||
|
||||
[FILL_FORM]: (state, {payload}) => {
|
||||
const { item, theme } = payload
|
||||
return this.normalize(item, theme)
|
||||
},
|
||||
|
||||
[[`${HISTORY} fulfilled`]]: (state, {payload}) => {
|
||||
const { data, limit, page, totalCount } = payload
|
||||
const entities = state.getIn(['sendHistory', 'entities']).concat(data)
|
||||
return state.mergeIn(['sendHistory'], {
|
||||
entities,
|
||||
limit,
|
||||
page,
|
||||
totalCount,
|
||||
isLoadingCompleted: true
|
||||
})
|
||||
},
|
||||
|
||||
[[`${HISTORY} pending`]]: (state, {payload}) => {
|
||||
return state.mergeIn(['sendHistory'], {
|
||||
isPending: payload.isPending
|
||||
})
|
||||
},
|
||||
|
||||
[CHANGE_SCHEDULE]: (state, {payload: {item, id}}) => {
|
||||
return state.mergeIn(['scheduling', 'times', id], {
|
||||
item
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import {ReduxModule} from '../../../abstract/reduxModule'
|
||||
import {switchShareSubScreen} from '../tabs'
|
||||
|
||||
export const FILL_FORM = 'Fill form'
|
||||
const CHANGE_FIELD = 'Change field'
|
||||
const CHOOSE_TAB = 'Choose tab'
|
||||
const TOGGLE_ACTIVE = 'Toggle active'
|
||||
const CLEAR = 'Clear form'
|
||||
const TOGGLE_SUBSCRIPTION = 'Toggle subscription'
|
||||
const TOGGLE_GROUP = 'Toggle group'
|
||||
const TOGGLE_RECIPIENT = 'Toggle recipient'
|
||||
|
||||
const CONFIRM_DELETE = 'Confirm delete'
|
||||
const CANCEL_DELETE = 'Cancel delete'
|
||||
const DELETE_ITEMS = 'Delete receiver'
|
||||
|
||||
export class ReceiverForm extends ReduxModule {
|
||||
|
||||
constructor (api) {
|
||||
super()
|
||||
this.api = api
|
||||
}
|
||||
|
||||
getTableTabs () {
|
||||
//implement in subclasses
|
||||
}
|
||||
|
||||
getInitialState () {
|
||||
const tableTabs = this.getTableTabs()
|
||||
return {
|
||||
active: true,
|
||||
tabs: {
|
||||
active: tableTabs[0],
|
||||
all: tableTabs
|
||||
},
|
||||
id: null,
|
||||
isDeletePopupVisible: false
|
||||
}
|
||||
}
|
||||
|
||||
toggleArrayField (actionName, fieldName) {
|
||||
return this.createHandler(actionName,
|
||||
(id, turnOn) => ({id, turnOn}),
|
||||
(state, {payload: {id, turnOn}}) => {
|
||||
let array = state.get(fieldName) //immutable!
|
||||
if (turnOn) {
|
||||
if (!array.includes(id)) {
|
||||
array = array.push(id)
|
||||
}
|
||||
} else {
|
||||
array = array.filter((item) => item !== id)
|
||||
}
|
||||
return state.set(fieldName, array)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/** Delete only current item in form, function name is for compatibility with DeletePopup **/
|
||||
deleteItems = (ids, {token, fulfilled, getState, dispatch}) => {
|
||||
return this.api
|
||||
.deleteItems(token, {ids})
|
||||
.then(() => {
|
||||
dispatch(switchShareSubScreen('recipients', 'tables'))
|
||||
dispatch(this.cancelDelete())
|
||||
})
|
||||
};
|
||||
|
||||
defineActions () {
|
||||
|
||||
const chooseTableTab = this.setIn(CHOOSE_TAB, ['tabs', 'active'])
|
||||
const toggleActive = this.toggle(TOGGLE_ACTIVE, 'active')
|
||||
|
||||
const clearForm = this.resetToInitialState(CLEAR)
|
||||
const fillForm = this.createAction(FILL_FORM, item => item)
|
||||
|
||||
const changeField = this.setField(CHANGE_FIELD)
|
||||
const toggleSubscription = this.toggleArrayField(TOGGLE_SUBSCRIPTION, 'subscriptions')
|
||||
const toggleRecipient = this.toggleArrayField(TOGGLE_RECIPIENT, 'recipients')
|
||||
const toggleGroup = this.toggleArrayField(TOGGLE_GROUP, 'groups')
|
||||
|
||||
const confirmDelete = this.reset(CONFIRM_DELETE, 'isDeletePopupVisible', true)
|
||||
this.cancelDelete = this.reset(CANCEL_DELETE, 'isDeletePopupVisible', false)
|
||||
const deleteItems = this.thunkAction(DELETE_ITEMS, this.deleteItems)
|
||||
|
||||
return {
|
||||
clearForm,
|
||||
changeField,
|
||||
chooseTableTab,
|
||||
toggleActive,
|
||||
fillForm,
|
||||
toggleSubscription,
|
||||
toggleRecipient,
|
||||
toggleGroup,
|
||||
confirmDelete,
|
||||
cancelDelete: this.cancelDelete,
|
||||
deleteItems
|
||||
}
|
||||
}
|
||||
|
||||
defineReducers () {
|
||||
return {
|
||||
[FILL_FORM]: (state, {payload: item}) => {
|
||||
return this.normalize(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import {ReceiverForm} from './receiverForm'
|
||||
import {
|
||||
switchShareSubScreen, switchShareTable, RECEIVER_SUBSCREENS, RECEIVER_TABLES, RECIPIENT_FORM_TABLES
|
||||
} from '../tabs'
|
||||
import {fromJS} from 'immutable'
|
||||
import * as api from '../../../../../api/recipientsApi'
|
||||
import {addAlert} from '../../../common/alerts'
|
||||
|
||||
const SAVE_RECIPIENT = 'Save recipient'
|
||||
|
||||
export class RecipientForm extends ReceiverForm {
|
||||
|
||||
constructor () {
|
||||
super(api)
|
||||
}
|
||||
|
||||
getNamespace () {
|
||||
return '[Recipient Form]'
|
||||
}
|
||||
|
||||
serialize (data) {
|
||||
return {
|
||||
firstName: data.firstName,
|
||||
lastName: data.lastName,
|
||||
email: data.email,
|
||||
active: data.active,
|
||||
notifications: data.subscriptions,
|
||||
groups: data.groups
|
||||
}
|
||||
};
|
||||
|
||||
normalize (data) {
|
||||
let newState = this.getInitialState()
|
||||
newState = {
|
||||
...newState,
|
||||
id: data.id,
|
||||
firstName: data.firstName,
|
||||
lastName: data.lastName,
|
||||
email: data.email,
|
||||
active: data.active,
|
||||
groups: data.groups.map(group => group.id),
|
||||
subscriptions: data.subscriptions.ids
|
||||
}
|
||||
return fromJS(newState)
|
||||
};
|
||||
|
||||
saveRecipient = ({dispatch, fulfilled, getState, token}) => {
|
||||
const data = getState().getIn(['appState', 'share', 'forms', 'recipient']).toJS()
|
||||
const id = data.id
|
||||
|
||||
if (data.firstName && data.lastName && data.email) {
|
||||
const payload = this.serialize(data)
|
||||
const apiRequest = id ? api.updateItem(token, payload, id) : api.createItem(token, payload)
|
||||
return apiRequest.then(() => {
|
||||
fulfilled()
|
||||
dispatch(addAlert({type: 'notice', transKey: 'recipientSaved'}))
|
||||
dispatch(switchShareSubScreen('recipients', RECEIVER_SUBSCREENS.TABLES))
|
||||
dispatch(switchShareTable('recipients', RECEIVER_TABLES.RECIPIENTS))
|
||||
})
|
||||
|
||||
} else {
|
||||
dispatch(addAlert({type: 'error', transKey: 'recipientNamesEmpty'}))
|
||||
}
|
||||
};
|
||||
|
||||
defineActions () {
|
||||
const saveReceiver = this.thunkAction(SAVE_RECIPIENT, this.saveRecipient, true)
|
||||
|
||||
return {
|
||||
...super.defineActions(),
|
||||
saveReceiver,
|
||||
deleteReceiver: this.deleteRecipient
|
||||
}
|
||||
}
|
||||
|
||||
getTableTabs () {
|
||||
return [RECIPIENT_FORM_TABLES.SUBSCRIPTIONS, RECIPIENT_FORM_TABLES.GROUPS, RECIPIENT_FORM_TABLES.EMAIL_HISTORY]
|
||||
}
|
||||
|
||||
getInitialState () {
|
||||
const state = super.getInitialState()
|
||||
return {
|
||||
...state,
|
||||
creationDate: '',
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
active: true,
|
||||
subscriptions: [],
|
||||
groups: []
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const instance = new RecipientForm()
|
||||
instance.init()
|
||||
export default instance
|
||||
@@ -0,0 +1,130 @@
|
||||
import {tokenInject} from '../../../utils/common'
|
||||
import {
|
||||
NOTIFICATION_SUBSCREENS, RECEIVER_SUBSCREENS, switchShareSubScreen,
|
||||
NOTIFICATION_TABLES
|
||||
} from './tabs'
|
||||
import { addAlert } from '../../common/alerts'
|
||||
import { getDefaultTheme } from './emailThemes/themes'
|
||||
|
||||
import myEmailsTable from './tables/myEmailsTable'
|
||||
import publishedEmailsTable from './tables/publishedEmailsTable'
|
||||
import emailsTable from './tables/emailsTable'
|
||||
|
||||
import alertForm from './forms/alertForm'
|
||||
import newsletterForm from './forms/newsletterForm'
|
||||
import recipientForm from './forms/recipientForm'
|
||||
import groupForm from './forms/groupForm'
|
||||
|
||||
import * as notificationsApi from '../../../../api/notificationsApi'
|
||||
|
||||
const tablePendingActions = {
|
||||
[NOTIFICATION_TABLES.MY_EMAILS]: myEmailsTable.asyncActionPending,
|
||||
[NOTIFICATION_TABLES.PUBLISHED]: publishedEmailsTable.asyncActionPending,
|
||||
emails: emailsTable.asyncActionPending
|
||||
}
|
||||
|
||||
//**** ACTIONS ****//
|
||||
|
||||
const loadTablePending = (type, isPending) => {
|
||||
return tablePendingActions[type](isPending)
|
||||
}
|
||||
|
||||
const startEditNotification = (notification, table, tab = 'notifications') => {
|
||||
return tokenInject((dispatch, getState, token) => {
|
||||
if (notification.type === 'alert') {
|
||||
|
||||
dispatch(loadTablePending(table, true))
|
||||
|
||||
dispatch(getDefaultTheme())
|
||||
.then((defaultTheme) => {
|
||||
return notificationsApi.getItem(token, null, notification.id)
|
||||
.then((fullNotification) => {
|
||||
dispatch(loadTablePending(table, false))
|
||||
dispatch(alertForm.actions.fillForm(fullNotification, defaultTheme))
|
||||
dispatch(switchShareSubScreen(tab, NOTIFICATION_SUBSCREENS.ALERT_FORM))
|
||||
})
|
||||
})
|
||||
.catch((errors) => {
|
||||
dispatch(loadTablePending(table, false))
|
||||
dispatch(addAlert(errors))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const startCreateNotification = (type, table, tab = 'notifications') => {
|
||||
return (dispatch, getState) => {
|
||||
|
||||
const recipient = getState().getIn(['common', 'auth', 'user', 'recipient']);
|
||||
if (!recipient) {
|
||||
return dispatch(addAlert('Please create at least one recipient from Manage Recipients to create an alert.'));
|
||||
}
|
||||
const myself = recipient.toJS()
|
||||
const defaultRecipient = {
|
||||
value: myself.id,
|
||||
label: `${myself.firstName} ${myself.lastName} <${myself.email}>`
|
||||
}
|
||||
|
||||
dispatch(loadTablePending(table, true))
|
||||
dispatch(getDefaultTheme())
|
||||
.then(() => {
|
||||
dispatch(loadTablePending(table, false))
|
||||
if (type === NOTIFICATION_SUBSCREENS.ALERT_FORM) {
|
||||
dispatch(alertForm.actions.clearForm())
|
||||
dispatch(alertForm.actions.changeRecipients([defaultRecipient]))
|
||||
dispatch(switchShareSubScreen(tab, NOTIFICATION_SUBSCREENS.ALERT_FORM))
|
||||
}
|
||||
else if (type === NOTIFICATION_SUBSCREENS.NEWSLETTER_FORM) {
|
||||
dispatch(newsletterForm.actions.clearForm())
|
||||
dispatch(switchShareSubScreen(tab, NOTIFICATION_SUBSCREENS.NEWSLETTER_FORM))
|
||||
}
|
||||
})
|
||||
.catch((errors) => {
|
||||
dispatch(loadTablePending(table, false))
|
||||
dispatch(addAlert(errors))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const startCreateRecipient = () => {
|
||||
return (dispatch) => {
|
||||
dispatch(switchShareSubScreen('recipients', RECEIVER_SUBSCREENS.RECIPIENT_FORM))
|
||||
dispatch(recipientForm.actions.clearForm())
|
||||
}
|
||||
}
|
||||
|
||||
const startCreateGroup = () => {
|
||||
return (dispatch) => {
|
||||
dispatch(switchShareSubScreen('recipients', RECEIVER_SUBSCREENS.GROUP_FORM))
|
||||
dispatch(groupForm.actions.clearForm())
|
||||
}
|
||||
}
|
||||
|
||||
const startEditRecipient = (item) => {
|
||||
console.log('start edit')
|
||||
return (dispatch) => {
|
||||
dispatch(switchShareSubScreen('recipients', RECEIVER_SUBSCREENS.RECIPIENT_FORM))
|
||||
dispatch(recipientForm.actions.clearForm())
|
||||
dispatch(recipientForm.actions.fillForm(item))
|
||||
}
|
||||
}
|
||||
|
||||
const startEditGroup = (item) => {
|
||||
return (dispatch) => {
|
||||
dispatch(switchShareSubScreen('recipients', RECEIVER_SUBSCREENS.GROUP_FORM))
|
||||
dispatch(groupForm.actions.clearForm())
|
||||
dispatch(groupForm.actions.fillForm(item))
|
||||
}
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
startEditNotification,
|
||||
startCreateNotification,
|
||||
startCreateRecipient,
|
||||
startCreateGroup,
|
||||
startEditRecipient,
|
||||
startEditGroup
|
||||
}
|
||||
|
||||
//**** This module has no state (: ****//
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import GenericTable from './genericTable'
|
||||
import * as api from '../../../../../api/notificationsApi'
|
||||
|
||||
class EmailFiltersTable extends GenericTable {
|
||||
|
||||
constructor () {
|
||||
super(api)
|
||||
}
|
||||
|
||||
getNamespace () {
|
||||
return '[Email filters]'
|
||||
}
|
||||
|
||||
getItems = (token, payload) => {
|
||||
return this.api.getFilters(token, payload)
|
||||
};
|
||||
|
||||
getDataFromResponse (response) {
|
||||
return response['filters']
|
||||
}
|
||||
|
||||
getTableState (state) {
|
||||
return state.getIn(['appState', 'share', 'tables', 'emailFilters'])
|
||||
}
|
||||
|
||||
getLoadTableRequestPayload (tableState) {
|
||||
return {
|
||||
...super.getLoadTableRequestPayload(tableState),
|
||||
type: tableState.filterType
|
||||
}
|
||||
}
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
...super.getInitialState(),
|
||||
filterType: 'owner'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const instance = new EmailFiltersTable()
|
||||
instance.init()
|
||||
export default instance
|
||||
@@ -0,0 +1,67 @@
|
||||
import GenericTable from './genericTable'
|
||||
import * as api from '../../../../../api/notificationsApi'
|
||||
|
||||
const SET_FILTER = 'Set filter'
|
||||
const CLEAR_FILTER = 'Clear filter'
|
||||
|
||||
class EmailsTable extends GenericTable {
|
||||
|
||||
constructor () {
|
||||
super(api)
|
||||
this.updateRestrictionsAfterDelete = true
|
||||
}
|
||||
|
||||
getNamespace () {
|
||||
return '[Emails]'
|
||||
}
|
||||
|
||||
getTableState (state) {
|
||||
return state.getIn(['appState', 'share', 'tables', 'emails'])
|
||||
}
|
||||
|
||||
getItems = (token, payload) => {
|
||||
return this.api.getAllItems(token, payload)
|
||||
}
|
||||
|
||||
getLoadTableRequestPayload (tableState) {
|
||||
const payload = super.getLoadTableRequestPayload(tableState)
|
||||
if (tableState.filter) {
|
||||
payload.filterId = tableState.filter.id
|
||||
payload.filterType = tableState.filter.type
|
||||
}
|
||||
delete payload.filter
|
||||
return payload
|
||||
}
|
||||
|
||||
defineActions () {
|
||||
|
||||
const setFilter = this.createAction(SET_FILTER)
|
||||
const clearFilter = this.createAction(CLEAR_FILTER)
|
||||
|
||||
return {
|
||||
...super.defineActions(),
|
||||
setFilter,
|
||||
clearFilter
|
||||
}
|
||||
}
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
...super.getInitialState(),
|
||||
filter: null
|
||||
}
|
||||
}
|
||||
|
||||
defineReducers () {
|
||||
return {
|
||||
...super.defineReducers(),
|
||||
[SET_FILTER]: this.setReducer('filter'),
|
||||
[CLEAR_FILTER]: this.resetReducer('filter', null)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const instance = new EmailsTable()
|
||||
instance.init()
|
||||
export default instance
|
||||
@@ -0,0 +1,268 @@
|
||||
import ReduxModule from '../../../abstract/reduxModule'
|
||||
import { addAlert } from '../../../common/alerts'
|
||||
import {tokenInject} from '../../../../utils/common'
|
||||
import {getRestrictions} from '../../../common/auth'
|
||||
|
||||
const SELECT_TABLE_ROW = 'Select table row'
|
||||
const SELECT_TABLE_ALL_ROWS = 'Select all rows'
|
||||
const SET_TABLE_PARAMS = 'Set table parameters'
|
||||
const LOAD_TABLE = 'Load table'
|
||||
const CONFIRM_DELETE = 'Confirm delete'
|
||||
const CANCEL_DELETE = 'Cancel delete'
|
||||
const CHANGE_FILTERED = 'Change filtered'
|
||||
const TOGGLE_FIELD = 'Toggle field'
|
||||
const ASYNC_ACTION = 'Async action'
|
||||
const DELETE_ITEMS = 'Delete items'
|
||||
|
||||
export default class GenericTableModule extends ReduxModule {
|
||||
|
||||
constructor (api) {
|
||||
super()
|
||||
this.api = api
|
||||
this.updateRestrictionsAfterDelete = false
|
||||
}
|
||||
|
||||
/*
|
||||
state - full state
|
||||
return - table state
|
||||
*/
|
||||
getTableState (state) {
|
||||
//implement in subclasses
|
||||
}
|
||||
|
||||
asyncToggleFieldAction (apiMethod, optionName) {
|
||||
return (ids, optionValue) => {
|
||||
return tokenInject((dispatch, getState, token) => {
|
||||
const payload = {
|
||||
ids,
|
||||
[optionName]: optionValue
|
||||
}
|
||||
|
||||
dispatch(this.asyncActionPending(true))
|
||||
apiMethod(token, payload)
|
||||
.then(() => {
|
||||
dispatch(this.toggleField(ids, optionName, optionValue))
|
||||
dispatch(this.asyncActionPending(false))
|
||||
})
|
||||
.catch((error) => {
|
||||
dispatch(this.asyncActionPending(false))
|
||||
dispatch(addAlert(error))
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_fixForDeleteFromLastPage (dispatch, getState, ids) {
|
||||
const tableState = this.getTableState(getState()).toJS()
|
||||
const totalPages = Math.ceil(tableState.totalCount / tableState.limit)
|
||||
if (tableState.page === totalPages && totalPages !== 1) { //we are on the last page
|
||||
const itemsOnLastPage = tableState.totalCount % tableState.limit
|
||||
const itemsToDelete = Object.keys(ids).length
|
||||
if (itemsOnLastPage === itemsToDelete) { //and we are deleting everything
|
||||
const newPage = tableState.page - 1
|
||||
dispatch(this.setTableParams({page: newPage}))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deleteItems = (ids, {token, fulfilled, getState, dispatch}) => {
|
||||
return this.api
|
||||
.deleteItems(token, {ids})
|
||||
.then(() => {
|
||||
this._fixForDeleteFromLastPage(dispatch, getState, ids)
|
||||
const payload = this._getLoadPayload(getState)
|
||||
this.updateRestrictionsAfterDelete && dispatch(getRestrictions())
|
||||
return this.api
|
||||
.getItems(token, payload)
|
||||
.then((response) => {
|
||||
fulfilled(this.getDataFromResponse(response))
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
_getLoadPayload (getState) {
|
||||
const state = getState()
|
||||
const tableState = this.getTableState(state).toJS()
|
||||
return this.getLoadTableRequestPayload(tableState)
|
||||
}
|
||||
|
||||
getLoadTableRequestPayload (tableState) {
|
||||
const payload = {
|
||||
page: tableState.page,
|
||||
limit: tableState.limit,
|
||||
sortField: tableState.sortField,
|
||||
sortDirection: tableState.sortDirection
|
||||
}
|
||||
if (tableState.filter) {
|
||||
payload.filter = tableState.filter
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
getDataFromResponse (response) {
|
||||
return response['notifications']
|
||||
}
|
||||
|
||||
getItems = (token, payload) => {
|
||||
return this.api.getItems(token, payload)
|
||||
}
|
||||
|
||||
loadTable = (params, {dispatch, getState, token, fulfilled}) => {
|
||||
if (params) {
|
||||
dispatch(this.setTableParams(params))
|
||||
}
|
||||
const payload = this._getLoadPayload(getState)
|
||||
return this.getItems(token, payload)
|
||||
.then((response) => {
|
||||
fulfilled(this.getDataFromResponse(response))
|
||||
})
|
||||
};
|
||||
|
||||
defineActions () {
|
||||
|
||||
const selectTableRow = this.createAction(SELECT_TABLE_ROW, (itemId) => itemId)
|
||||
const selectTableAllRows = this.createAction(SELECT_TABLE_ALL_ROWS)
|
||||
const confirmDelete = this.createAction(CONFIRM_DELETE)
|
||||
const cancelDelete = this.createAction(CANCEL_DELETE)
|
||||
const changeTableFiltered = this.createAction(CHANGE_FILTERED)
|
||||
|
||||
const setTableParams = this.createAction(SET_TABLE_PARAMS, (params) => params)
|
||||
this.setTableParams = setTableParams
|
||||
const loadTable = this.thunkAction(LOAD_TABLE, this.loadTable, true)
|
||||
const deleteItems = this.thunkAction(DELETE_ITEMS, this.deleteItems, true)
|
||||
|
||||
this.asyncActionPending = this.createAction(`${ASYNC_ACTION} pending`, (value) => ({isPending: value}))
|
||||
this.toggleField = this.createAction(TOGGLE_FIELD, (ids, fieldName, fieldValue) => ({ids, fieldName, fieldValue}))
|
||||
|
||||
const toggleActive = this.asyncToggleFieldAction(this.api.activateItems, 'active')
|
||||
const togglePublish = this.asyncToggleFieldAction(this.api.publishItems, 'published')
|
||||
|
||||
return {
|
||||
loadTable,
|
||||
selectTableRow,
|
||||
selectTableAllRows,
|
||||
setTableParams,
|
||||
confirmDelete,
|
||||
cancelDelete,
|
||||
changeTableFiltered,
|
||||
toggleActive,
|
||||
togglePublish,
|
||||
deleteItems
|
||||
}
|
||||
}
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
sortField: 'name',
|
||||
sortDirection: 'asc',
|
||||
data: [],
|
||||
count: 0,
|
||||
totalCount: 0,
|
||||
isLoading: false,
|
||||
isAllSelected: false,
|
||||
selectedIds: [],
|
||||
idsToDelete: {},
|
||||
isDeletePopupVisible: false
|
||||
}
|
||||
}
|
||||
|
||||
/** REDUCERS **/
|
||||
|
||||
onSelectTableRow (state, {payload: itemId}) {
|
||||
let selectedIds = state.get('selectedIds')
|
||||
const isSelected = selectedIds.includes(itemId)
|
||||
if (isSelected) {
|
||||
selectedIds = selectedIds.filter(id => id !== itemId)
|
||||
}
|
||||
else {
|
||||
selectedIds = selectedIds.push(itemId)
|
||||
}
|
||||
return state.merge({
|
||||
'selectedIds': selectedIds,
|
||||
'isAllSelected': false
|
||||
})
|
||||
}
|
||||
|
||||
onSelectAllTableRows (state) {
|
||||
const isAllSelected = state.get('isAllSelected')
|
||||
if (isAllSelected) { //then deselect all
|
||||
return state.merge({
|
||||
isAllSelected: false,
|
||||
selectedIds: []
|
||||
})
|
||||
}
|
||||
else { //select all currently loaded data
|
||||
const selectedIds = state.get('data').map(item => item.get('id'))
|
||||
return state.merge({
|
||||
isAllSelected: true,
|
||||
selectedIds
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
onLoadFulfilled (state, {payload: response}) {
|
||||
return state.merge({
|
||||
data: response.data,
|
||||
count: response.count,
|
||||
totalCount: response.totalCount
|
||||
})
|
||||
}
|
||||
|
||||
onDeleteItemsFulfilled (state, {payload: response}) {
|
||||
return state.merge({
|
||||
data: response.data,
|
||||
count: response.count,
|
||||
totalCount: response.totalCount,
|
||||
selectedIds: []
|
||||
})
|
||||
};
|
||||
|
||||
onConfirmDelete (state, {payload: ids}) {
|
||||
return state.merge({
|
||||
isDeletePopupVisible: true,
|
||||
idsToDelete: ids
|
||||
})
|
||||
}
|
||||
|
||||
onCancelDelete (state) {
|
||||
return state.merge({
|
||||
isDeletePopupVisible: false,
|
||||
idsToDelete: {}
|
||||
})
|
||||
}
|
||||
|
||||
onAsyncActionPending (state, {payload: {isPending}}) {
|
||||
return state.merge({
|
||||
isLoading: isPending,
|
||||
isDeletePopupVisible: false //dirty hack, need to think 'bout it
|
||||
})
|
||||
}
|
||||
|
||||
onToggleField (state, {payload: {ids, fieldName, fieldValue}}) {
|
||||
let tableData = state.get('data')
|
||||
const newValues = {[fieldName]: fieldValue}
|
||||
tableData = tableData.map((item) => (ids.includes(item.get('id'))) ? item.merge(newValues) : item)
|
||||
return state.set('data', tableData)
|
||||
}
|
||||
|
||||
defineReducers () {
|
||||
return {
|
||||
[SELECT_TABLE_ROW]: this.onSelectTableRow,
|
||||
[SELECT_TABLE_ALL_ROWS]: this.onSelectAllTableRows,
|
||||
[SET_TABLE_PARAMS]: this.mergeReducer(),
|
||||
[`${LOAD_TABLE} pending`]: this.thunkPendingReducer('isLoading'),
|
||||
[`${LOAD_TABLE} fulfilled`]: this.onLoadFulfilled,
|
||||
[`${ASYNC_ACTION} pending`]: this.onAsyncActionPending,
|
||||
[`${DELETE_ITEMS} pending`]: this.onAsyncActionPending,
|
||||
[`${DELETE_ITEMS} fulfilled`]: this.onDeleteItemsFulfilled,
|
||||
[TOGGLE_FIELD]: this.onToggleField,
|
||||
[CONFIRM_DELETE]: this.onConfirmDelete,
|
||||
[CANCEL_DELETE]: this.onCancelDelete,
|
||||
[CHANGE_FILTERED]: this.setReducer('filtered')
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import GenericTable from './genericTable'
|
||||
import * as api from '../../../../../api/groupsApi'
|
||||
|
||||
class GroupsTable extends GenericTable {
|
||||
|
||||
constructor () {
|
||||
super(api)
|
||||
}
|
||||
|
||||
getNamespace () {
|
||||
return '[Groups table]'
|
||||
}
|
||||
|
||||
getInitialState () {
|
||||
const state = super.getInitialState()
|
||||
return {
|
||||
...state,
|
||||
filter: ''
|
||||
}
|
||||
}
|
||||
|
||||
getDataFromResponse (response) {
|
||||
return response['groups']
|
||||
}
|
||||
|
||||
getTableState (state) {
|
||||
return state.getIn(['appState', 'share', 'tables', 'groups'])
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const instance = new GroupsTable()
|
||||
instance.init()
|
||||
export default instance
|
||||
@@ -0,0 +1,30 @@
|
||||
import GenericTable from './genericTable'
|
||||
import * as api from '../../../../../api/notificationsApi'
|
||||
|
||||
class MyEmailsTable extends GenericTable {
|
||||
|
||||
constructor () {
|
||||
super(api)
|
||||
this.updateRestrictionsAfterDelete = true
|
||||
}
|
||||
|
||||
getNamespace () {
|
||||
return '[My Emails]'
|
||||
}
|
||||
|
||||
getTableState (state) {
|
||||
return state.getIn(['appState', 'share', 'tables', 'myEmails'])
|
||||
}
|
||||
|
||||
getLoadTableRequestPayload (tableState) {
|
||||
return {
|
||||
...super.getLoadTableRequestPayload(tableState),
|
||||
onlyPublished: false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const instance = new MyEmailsTable()
|
||||
instance.init()
|
||||
export default instance
|
||||
@@ -0,0 +1,38 @@
|
||||
import GenericTableModule from './genericTable'
|
||||
import * as api from '../../../../../api/notificationsApi'
|
||||
|
||||
class PublishedEmailsTable extends GenericTableModule {
|
||||
|
||||
constructor () {
|
||||
super(api)
|
||||
this.updateRestrictionsAfterDelete = true
|
||||
}
|
||||
|
||||
getNamespace () {
|
||||
return '[Published Emails]'
|
||||
}
|
||||
|
||||
getTableState (state) {
|
||||
return state.getIn(['appState', 'share', 'tables', 'publishedEmails'])
|
||||
}
|
||||
|
||||
getLoadTableRequestPayload (tableState) {
|
||||
return {
|
||||
...super.getLoadTableRequestPayload(tableState),
|
||||
onlyPublished: true
|
||||
}
|
||||
}
|
||||
|
||||
defineActions () {
|
||||
const toggleSubscribe = this.asyncToggleFieldAction(this.api.subscribeItems, 'subscribed')
|
||||
return {
|
||||
...super.defineActions(),
|
||||
toggleSubscribe
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const instance = new PublishedEmailsTable()
|
||||
instance.init()
|
||||
export default instance
|
||||
@@ -0,0 +1,34 @@
|
||||
import * as api from '../../../../../../api/receiversApi'
|
||||
import GenericTableModule from '../genericTable'
|
||||
|
||||
class EmailHistory extends GenericTableModule {
|
||||
|
||||
constructor () {
|
||||
super(api)
|
||||
}
|
||||
|
||||
getNamespace () {
|
||||
return '[Email history]'
|
||||
}
|
||||
|
||||
getTableState (state) {
|
||||
return state.getIn(['appState', 'share', 'tables', 'receiverForm', 'emailHistory'])
|
||||
}
|
||||
|
||||
loadTable = (params, receiver, {dispatch, getState, token, fulfilled}) => {
|
||||
if (params) {
|
||||
dispatch(this.setTableParams(params))
|
||||
}
|
||||
const payload = this._getLoadPayload(getState)
|
||||
return this.api
|
||||
.getEmailHistory(token, payload, receiver.id)
|
||||
.then((response) => {
|
||||
fulfilled(response)
|
||||
})
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
const instance = new EmailHistory()
|
||||
instance.init()
|
||||
export default instance
|
||||
@@ -0,0 +1,84 @@
|
||||
import {fromJS} from 'immutable'
|
||||
import GenericTableModule from '../genericTable'
|
||||
|
||||
const TOGGLE_SUBSCRIBED = 'Toggle subscribed'
|
||||
const TOGGLE_ENROLLED = 'Toggle enrolled'
|
||||
|
||||
export default class ReceiverFormTable extends GenericTableModule {
|
||||
|
||||
getDataFromResponse (response) {
|
||||
//implement in subclasses
|
||||
}
|
||||
|
||||
getLoadTableRequestPayload (tableState, receiver) {
|
||||
let payload = super.getLoadTableRequestPayload(tableState)
|
||||
if (tableState.filter) {
|
||||
payload.filter = tableState.filter
|
||||
}
|
||||
const statusFilter = tableState.statusFilter
|
||||
if (statusFilter) {
|
||||
payload.statusFilter = tableState.statusFilter
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
_getLoadPayload (getState, receiver) {
|
||||
const tableState = this.getTableState(getState()).toJS()
|
||||
return this.getLoadTableRequestPayload(tableState, receiver)
|
||||
}
|
||||
|
||||
loadTable = (params, receiver, {dispatch, getState, token, fulfilled}) => {
|
||||
if (params) {
|
||||
dispatch(this.setTableParams(params))
|
||||
}
|
||||
const payload = this._getLoadPayload(getState, receiver) // <-- difference from genericTable
|
||||
return this.api
|
||||
.getItems(token, payload)
|
||||
.then((response) => {
|
||||
fulfilled(this.getDataFromResponse(response, receiver))
|
||||
})
|
||||
};
|
||||
|
||||
addDataColumn (data, fieldName, ids = []) {
|
||||
data.forEach((item) => {
|
||||
item[fieldName] = ids.includes(item.id)
|
||||
})
|
||||
};
|
||||
|
||||
toggleDataField (actionName, fieldName) {
|
||||
return this.createHandler(
|
||||
actionName,
|
||||
(itemId, turnOn) => ({itemId, turnOn}),
|
||||
(state, {payload: {itemId, turnOn}}) => {
|
||||
const tableData = state.get('data').toJS()
|
||||
tableData.forEach((item) => {
|
||||
if (item.id === itemId) {
|
||||
item[fieldName] = turnOn
|
||||
}
|
||||
})
|
||||
return state.set('data', fromJS(tableData))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
defineActions () {
|
||||
const actions = super.defineActions()
|
||||
const toggleSubscribed = this.toggleDataField(TOGGLE_SUBSCRIBED, 'subscribed')
|
||||
const toggleEnrolled = this.toggleDataField(TOGGLE_ENROLLED, 'enrolled')
|
||||
|
||||
return {
|
||||
...actions,
|
||||
toggleSubscribed,
|
||||
toggleEnrolled
|
||||
}
|
||||
}
|
||||
|
||||
getInitialState () {
|
||||
return {
|
||||
...super.getInitialState(),
|
||||
filter: '',
|
||||
statusFilter: 'all'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import ReceiverFormTable from './receiverFormTable'
|
||||
import * as api from '../../../../../../api/groupsApi'
|
||||
|
||||
///api/v1/recipients/groups with recipientId
|
||||
|
||||
class ReceiverGroupsTable extends ReceiverFormTable {
|
||||
|
||||
constructor () {
|
||||
super(api)
|
||||
}
|
||||
|
||||
getNamespace () {
|
||||
return '[Recipient form groups table]'
|
||||
}
|
||||
|
||||
getTableState (state) {
|
||||
return state.getIn(['appState', 'share', 'tables', 'receiverForm', 'groups'])
|
||||
}
|
||||
|
||||
getLoadTableRequestPayload (tableState, receiver) {
|
||||
let payload = super.getLoadTableRequestPayload(tableState)
|
||||
if (receiver) {
|
||||
payload.recipientId = receiver.id
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
getDataFromResponse (response, receiver) {
|
||||
const data = response.groups
|
||||
this.addDataColumn(data.data, 'enrolled', receiver.groups)
|
||||
return data
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const instance = new ReceiverGroupsTable()
|
||||
instance.init()
|
||||
export default instance
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import ReceiverFormTable from './receiverFormTable'
|
||||
import * as api from '../../../../../../api/recipientsApi'
|
||||
|
||||
///api/v1/recipients with groupId
|
||||
|
||||
class ReceiverRecipientsTable extends ReceiverFormTable {
|
||||
|
||||
constructor () {
|
||||
super(api)
|
||||
}
|
||||
|
||||
getNamespace () {
|
||||
return '[Group form recipients table]'
|
||||
}
|
||||
|
||||
getTableState (state) {
|
||||
return state.getIn(['appState', 'share', 'tables', 'receiverForm', 'recipients'])
|
||||
}
|
||||
|
||||
getLoadTableRequestPayload (tableState, receiver) {
|
||||
let payload = super.getLoadTableRequestPayload(tableState)
|
||||
if (receiver) {
|
||||
payload.groupId = receiver.id
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
getDataFromResponse (response, receiver) {
|
||||
const data = response['recipients']
|
||||
this.addDataColumn(data.data, 'enrolled', receiver.recipients)
|
||||
return data
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new ReceiverRecipientsTable()
|
||||
instance.init()
|
||||
export default instance
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import ReceiverFormTable from './receiverFormTable'
|
||||
import * as api from '../../../../../../api/notificationsApi'
|
||||
|
||||
class ReceiverSubscriptionsTable extends ReceiverFormTable {
|
||||
|
||||
constructor () {
|
||||
super(api)
|
||||
}
|
||||
|
||||
getNamespace () {
|
||||
return '[Receiver form subscriptions table]'
|
||||
}
|
||||
|
||||
getTableState (state) {
|
||||
return state.getIn(['appState', 'share', 'tables', 'receiverForm', 'subscriptions'])
|
||||
}
|
||||
|
||||
getLoadTableRequestPayload (tableState, receiver) {
|
||||
let payload = super.getLoadTableRequestPayload(tableState)
|
||||
if (receiver) {
|
||||
payload.entityId = receiver.id
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
getDataFromResponse (response, receiver) {
|
||||
const data = response.notifications
|
||||
this.addDataColumn(data.data, 'subscribed', receiver.subscriptions)
|
||||
return data
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const instance = new ReceiverSubscriptionsTable()
|
||||
instance.init()
|
||||
export default instance
|
||||
@@ -0,0 +1,34 @@
|
||||
import GenericTable from './genericTable'
|
||||
import * as api from '../../../../../api/recipientsApi'
|
||||
|
||||
class RecipientsTable extends GenericTable {
|
||||
|
||||
constructor () {
|
||||
super(api)
|
||||
}
|
||||
|
||||
getNamespace () {
|
||||
return '[Recipients table]'
|
||||
}
|
||||
|
||||
getInitialState () {
|
||||
const state = super.getInitialState()
|
||||
return {
|
||||
...state,
|
||||
filter: ''
|
||||
}
|
||||
}
|
||||
|
||||
getTableState (state) {
|
||||
return state.getIn(['appState', 'share', 'tables', 'recipients'])
|
||||
}
|
||||
|
||||
getDataFromResponse (response) {
|
||||
return response['recipients']
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const instance = new RecipientsTable()
|
||||
instance.init()
|
||||
export default instance
|
||||
@@ -0,0 +1,87 @@
|
||||
import { createAction, handleActions } from 'redux-actions'
|
||||
import { fromJS } from 'immutable'
|
||||
|
||||
//**** CONSTANTS ****//
|
||||
const NS = '[Share tab]'
|
||||
const SWITCH_SUBSCREEN = `${NS} Switch subscreen`
|
||||
const SWITCH_TABLE = `${NS} Switch table`
|
||||
|
||||
//**** ACTIONS ****//
|
||||
export const switchShareSubScreen = createAction(SWITCH_SUBSCREEN, (type, subScreen) => ({type, subScreen}))
|
||||
export const switchShareTable = createAction(SWITCH_TABLE, (type, table) => ({type, table}))
|
||||
|
||||
export const actions = {
|
||||
switchShareSubScreen,
|
||||
switchShareTable
|
||||
}
|
||||
|
||||
/* TABS SUBSCREENS */
|
||||
export const NOTIFICATION_SUBSCREENS = {
|
||||
TABLES: 'tables',
|
||||
ALERT_FORM: 'alert',
|
||||
NEWSLETTER_FORM: 'newsletter'
|
||||
}
|
||||
|
||||
export const RECEIVER_SUBSCREENS = {
|
||||
TABLES: 'tables',
|
||||
RECIPIENT_FORM: 'recipient',
|
||||
GROUP_FORM: 'group'
|
||||
}
|
||||
|
||||
export const EMAILS_SUBSCREENS = {
|
||||
EMAILS_TABLE: 'table',
|
||||
ALERT_FORM: 'alert',
|
||||
NEWSLETTER_FORM: 'newsletter',
|
||||
FILTERS_TABLE: 'filters'
|
||||
}
|
||||
|
||||
/* TABLES IN 'tables' SUBSCREEN */
|
||||
export const NOTIFICATION_TABLES = {
|
||||
MY_EMAILS: 'myEmails',
|
||||
PUBLISHED: 'publishedEmails'
|
||||
}
|
||||
|
||||
export const RECEIVER_TABLES = {
|
||||
RECIPIENTS: 'recipients',
|
||||
GROUPS: 'groups'
|
||||
}
|
||||
|
||||
/* TABLES IN FORMS */
|
||||
export const RECIPIENT_FORM_TABLES = {
|
||||
EMAIL_HISTORY: 'emailHistory',
|
||||
SUBSCRIPTIONS: 'subscriptions',
|
||||
GROUPS: 'groups'
|
||||
}
|
||||
|
||||
export const GROUP_FORM_TABLES = {
|
||||
EMAIL_HISTORY: 'emailHistory',
|
||||
SUBSCRIPTIONS: 'subscriptions',
|
||||
RECIPIENTS: 'recipients'
|
||||
}
|
||||
|
||||
export const initialState = fromJS({
|
||||
notifications: {
|
||||
subScreenVisible: NOTIFICATION_SUBSCREENS.TABLES,
|
||||
tableVisible: 'myEmails'
|
||||
},
|
||||
recipients: {
|
||||
subScreenVisible: RECEIVER_SUBSCREENS.TABLES,
|
||||
tableVisible: 'recipients'
|
||||
},
|
||||
emails: {
|
||||
subScreenVisible: EMAILS_SUBSCREENS.EMAILS_TABLE
|
||||
}
|
||||
})
|
||||
|
||||
//**** REDUCERS ****//
|
||||
export default handleActions({
|
||||
[SWITCH_SUBSCREEN]: (state, {payload}) => {
|
||||
const { type, subScreen } = payload
|
||||
return state.setIn([type, 'subScreenVisible'], subScreen)
|
||||
},
|
||||
|
||||
[SWITCH_TABLE]: (state, {payload}) => {
|
||||
const { type, table } = payload
|
||||
return state.setIn([type, 'tableVisible'], table)
|
||||
}
|
||||
}, initialState)
|
||||
@@ -0,0 +1,325 @@
|
||||
import {createAction, handleActions} from 'redux-actions'
|
||||
import {fromJS} from 'immutable'
|
||||
import * as categoriesApi from '../../../api/sidebarCategoriesApi'
|
||||
import * as feedsApi from '../../../api/feedsApi'
|
||||
import {addAlert} from '../common/alerts'
|
||||
import {thunkAction} from '../../utils/common'
|
||||
import * as helpers from '../../utils/helpers/sidebar'
|
||||
import {getRestrictions} from '../common/auth'
|
||||
import {actions as searchActions} from './search'
|
||||
import {actions as searchFiltersActions} from './searchByFilters'
|
||||
|
||||
/*
|
||||
* Constants
|
||||
* */
|
||||
export const TYPES = {
|
||||
FOLDER: 'folder',
|
||||
FEED: 'feed',
|
||||
CLIP_ARTICLE: 'clipArticle'
|
||||
}
|
||||
|
||||
const NS = '[Sidebar]'
|
||||
const GET_SIDEBAR_CATEGORIES = `${NS} Get categories`
|
||||
const ADD_CLIPPINGS_FEED = `${NS} Add clippings feed`
|
||||
const ADD_CATEGORY = `${NS} Add category`
|
||||
const RENAME_FEED = `${NS} Rename feed`
|
||||
const RENAME_CATEGORY = `${NS} Rename category`
|
||||
const TOGGLE_EXPORT_FEED = `${NS} Toggle export feed`
|
||||
const TOGGLE_EXPORT_CATEGORY = `${NS} Toggle export category`
|
||||
const MOVE_FEED = `${NS} Move feed`
|
||||
const MOVE_CATEGORY = `${NS} Move category`
|
||||
const DELETE_FEED = `${NS} Delete feed`
|
||||
const DELETE_CATEGORY = `${NS} Delete category`
|
||||
const SET_CATEGORIES = `${NS} Set categories`
|
||||
|
||||
export const SET_FILTERED_CATEGORIES = 'SET_FILTERED_CATEGORIES'
|
||||
export const CLEAR_FILTERED_CATEGORIES = 'CLEAR_FILTERED_CATEGORIES'
|
||||
|
||||
export const SHOW_DELETE_POPUP = 'SHOW_DELETE_POPUP'
|
||||
export const HIDE_DELETE_POPUP = 'HIDE_DELETE_POPUP'
|
||||
export const SHOW_RENAME_POPUP = 'SHOW_RENAME_POPUP'
|
||||
export const HIDE_RENAME_POPUP = 'HIDE_RENAME_POPUP'
|
||||
export const SHOW_ADD_CATEGORY_POPUP = 'SHOW_ADD_CATEGORY_POPUP'
|
||||
export const HIDE_ADD_CATEGORY_POPUP = 'HIDE_ADD_CATEGORY_POPUP'
|
||||
|
||||
export const SHOW_ADD_CLIPPINGS_POPUP = 'SHOW_ADD_CLIPPINGS_POPUP'
|
||||
export const HIDE_ADD_CLIPPINGS_POPUP = 'HIDE_ADD_CLIPPINGS_POPUP'
|
||||
|
||||
/*
|
||||
* Actions
|
||||
* */
|
||||
export const getSidebarCategories = thunkAction(GET_SIDEBAR_CATEGORIES, ({token, fulfilled}) => {
|
||||
return categoriesApi
|
||||
.getCategories(token)
|
||||
.then((categories) => {
|
||||
fulfilled(categories)
|
||||
})
|
||||
})
|
||||
|
||||
export const setFilteredCategories = createAction(SET_FILTERED_CATEGORIES, (filteredCategories) => filteredCategories)
|
||||
|
||||
export const _clearFilteredCategories = createAction(CLEAR_FILTERED_CATEGORIES)
|
||||
|
||||
export const clearFilteredCategories = () => {
|
||||
return (dispatch, state) => {
|
||||
document.getElementById('sidebar-search').value = ''
|
||||
|
||||
dispatch(_clearFilteredCategories())
|
||||
}
|
||||
}
|
||||
|
||||
export const setCategories = createAction(SET_CATEGORIES, (changedCategories) => changedCategories)
|
||||
|
||||
export const showDeletePopup = createAction(SHOW_DELETE_POPUP, (itemId, itemType, itemName, parentId) => {
|
||||
return {itemId, itemType, itemName, parentId}
|
||||
})
|
||||
export const hideDeletePopup = createAction(HIDE_DELETE_POPUP)
|
||||
|
||||
export const showRenamePopup = createAction(SHOW_RENAME_POPUP, (itemId, itemType, itemName, parentId) => {
|
||||
return {itemId, itemType, itemName, parentId}
|
||||
})
|
||||
export const hideRenamePopup = createAction(HIDE_RENAME_POPUP)
|
||||
|
||||
export const showAddCategoryPopup = createAction(SHOW_ADD_CATEGORY_POPUP, (parentId) => ({parentId}))
|
||||
export const hideAddCategoryPopup = createAction(HIDE_ADD_CATEGORY_POPUP)
|
||||
|
||||
export const showAddClippingsFeedPopup = createAction(SHOW_ADD_CLIPPINGS_POPUP, (parentId) => ({parentId}))
|
||||
export const hideAddClippingsFeedPopup = createAction(HIDE_ADD_CLIPPINGS_POPUP)
|
||||
|
||||
export const addCategory = thunkAction(ADD_CATEGORY, (name, parentId, {token, dispatch, getState, fulfilled}) => {
|
||||
return categoriesApi
|
||||
.addCategory(token, {name: name, parent: parentId})
|
||||
.then((newCategory) => {
|
||||
fulfilled(newCategory)
|
||||
const newCategories = helpers.addCategory(getState(), parentId, newCategory)
|
||||
dispatch(setCategories(newCategories))
|
||||
dispatch(clearFilteredCategories())
|
||||
})
|
||||
})
|
||||
|
||||
export const moveCategory = thunkAction(MOVE_CATEGORY, (category, newCategoryId, {token, dispatch, fulfilled}) => {
|
||||
const draggedCategoryId = category.id
|
||||
const notCategoryItself = newCategoryId !== draggedCategoryId
|
||||
const notCategoryInChild = !helpers.checkIfDraggedCategoryDragToItsChild(category.childes, newCategoryId)
|
||||
// check if category trying to move to it's child or itself
|
||||
if (notCategoryItself && notCategoryInChild) {
|
||||
return categoriesApi
|
||||
.moveCategory(token, undefined, draggedCategoryId, newCategoryId)
|
||||
.then((response) => {
|
||||
fulfilled()
|
||||
const newCategories = fromJS(response.data)
|
||||
dispatch(setCategories(newCategories))
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
export const renameCategory = thunkAction(RENAME_CATEGORY, (categoryId, categoryName, parentId, {token, dispatch, getState, fulfilled}) => {
|
||||
return categoriesApi
|
||||
.renameCategory(token, {name: categoryName, parent: parentId}, categoryId)
|
||||
.then(() => {
|
||||
fulfilled()
|
||||
const newCategories = helpers.renameCategory(getState(), categoryId, categoryName)
|
||||
dispatch(setCategories(newCategories))
|
||||
dispatch(clearFilteredCategories())
|
||||
})
|
||||
})
|
||||
|
||||
export const deleteCategory = thunkAction(DELETE_CATEGORY, (categoryId, {token, dispatch, getState, fulfilled}) => {
|
||||
return categoriesApi
|
||||
.deleteCategory(token, undefined, categoryId)
|
||||
.then(() => {
|
||||
fulfilled()
|
||||
const newCategories = helpers.deleteCategory(getState(), categoryId)
|
||||
dispatch(setCategories(newCategories))
|
||||
dispatch(clearFilteredCategories())
|
||||
})
|
||||
})
|
||||
|
||||
export const addClippingsFeed = thunkAction(ADD_CLIPPINGS_FEED, (feedName, categoryId, {token, dispatch, fulfilled, getState}) => {
|
||||
const payload = {
|
||||
feed: {
|
||||
name: feedName,
|
||||
category: categoryId,
|
||||
subType: 'clip_feed'
|
||||
}
|
||||
}
|
||||
return feedsApi
|
||||
.createFeed(token, payload)
|
||||
.then((newFeed) => {
|
||||
fulfilled(newFeed)
|
||||
const newCategories = helpers.addFeed(getState(), categoryId, newFeed)
|
||||
dispatch(setCategories(newCategories))
|
||||
dispatch(getRestrictions())
|
||||
dispatch(addAlert({
|
||||
type: 'notice',
|
||||
transKey: 'saveFeed',
|
||||
id: 'saveFeed'
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
export const moveFeed = thunkAction(MOVE_FEED, (feedId, categoryId, {token, dispatch, fulfilled}) => {
|
||||
return feedsApi
|
||||
.moveFeed(token, undefined, feedId, categoryId)
|
||||
.then((response) => {
|
||||
fulfilled()
|
||||
const newCategories = fromJS(response.data)
|
||||
dispatch(setCategories(newCategories))
|
||||
})
|
||||
})
|
||||
|
||||
export const toggleExportFeed = thunkAction(TOGGLE_EXPORT_FEED, (feedId, isExported, {token, dispatch, fulfilled}) => {
|
||||
return feedsApi
|
||||
.toggleExportFeed(token, {export: isExported}, feedId)
|
||||
.then(() => {
|
||||
fulfilled()
|
||||
dispatch(getSidebarCategories())
|
||||
dispatch(getRestrictions())
|
||||
})
|
||||
})
|
||||
|
||||
const toggleExportCategory = thunkAction(TOGGLE_EXPORT_CATEGORY, (categoryId, isExported, {token, dispatch, fulfilled}) => {
|
||||
return feedsApi
|
||||
.toggleExportCategory(token, {export: isExported}, categoryId)
|
||||
.then(() => {
|
||||
fulfilled()
|
||||
dispatch(getSidebarCategories())
|
||||
})
|
||||
})
|
||||
|
||||
export const renameFeed = thunkAction(RENAME_FEED, (feedId, newName, parentId, {token, dispatch, fulfilled, getState}) => {
|
||||
return feedsApi
|
||||
.renameFeed(token, {name: newName}, feedId)
|
||||
.then(() => {
|
||||
fulfilled()
|
||||
const newCategories = helpers.renameFeed(getState(), feedId, newName, parentId)
|
||||
dispatch(setCategories(newCategories))
|
||||
})
|
||||
})
|
||||
|
||||
export const deleteFeed = thunkAction(DELETE_FEED, (feedId, categoryId, {token, dispatch, getState, fulfilled}) => {
|
||||
const currentFeedId = getState().getIn(['appState', 'search', 'activeFeed', 'id'])
|
||||
const isCurrent = currentFeedId && (parseInt(currentFeedId) === parseInt(feedId))
|
||||
return feedsApi
|
||||
.deleteFeed(token, undefined, feedId)
|
||||
.then(() => {
|
||||
fulfilled()
|
||||
if (isCurrent) {
|
||||
dispatch(searchActions.setNewSearch())
|
||||
dispatch(searchFiltersActions.renewSearchBy())
|
||||
}
|
||||
const newCategories = helpers.deleteFeed(getState(), categoryId, feedId)
|
||||
dispatch(setCategories(newCategories))
|
||||
dispatch(getRestrictions())
|
||||
dispatch(clearFilteredCategories())
|
||||
})
|
||||
})
|
||||
|
||||
export const actions = {
|
||||
getSidebarCategories,
|
||||
setFilteredCategories,
|
||||
clearFilteredCategories,
|
||||
setCategories,
|
||||
addCategory,
|
||||
addClippingsFeed,
|
||||
moveCategory,
|
||||
moveFeed,
|
||||
deleteFeed,
|
||||
deleteCategory,
|
||||
renameFeed,
|
||||
renameCategory,
|
||||
showDeletePopup,
|
||||
hideDeletePopup,
|
||||
showRenamePopup,
|
||||
hideRenamePopup,
|
||||
showAddCategoryPopup,
|
||||
hideAddCategoryPopup,
|
||||
showAddClippingsFeedPopup,
|
||||
hideAddClippingsFeedPopup,
|
||||
toggleExportFeed,
|
||||
toggleExportCategory
|
||||
}
|
||||
|
||||
/*
|
||||
* State
|
||||
* */
|
||||
export const initialState = fromJS({
|
||||
areCategoriesLoaded: false,
|
||||
categories: [],
|
||||
filteredCategories: [],
|
||||
areFeedsFiltered: false,
|
||||
popupVisible: {
|
||||
'delete': false,
|
||||
rename: false,
|
||||
addCategory: false,
|
||||
addClippingsFeed: false
|
||||
},
|
||||
popupItems: {
|
||||
'delete': {}, //feed or category
|
||||
rename: {}, //feed or category
|
||||
addCategory: {}, //{parentId}
|
||||
addClippingsFeed: {} //{parentId}
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* Reducers
|
||||
* */
|
||||
const hidePopup = (type) => (state) => {
|
||||
return state
|
||||
.setIn(['popupVisible', type], false)
|
||||
.setIn(['popupItems', type], {})
|
||||
}
|
||||
|
||||
const showPopup = (type) => (state, {payload}) => {
|
||||
return state
|
||||
.setIn(['popupVisible', type], true)
|
||||
.setIn(['popupItems', type], payload)
|
||||
}
|
||||
|
||||
export default handleActions({
|
||||
|
||||
[`${GET_SIDEBAR_CATEGORIES} fulfilled`]: (state, { payload }) => {
|
||||
const response = payload.data
|
||||
|
||||
return state.merge({
|
||||
'categories': response,
|
||||
'areCategoriesLoaded': true
|
||||
})
|
||||
},
|
||||
|
||||
[SET_FILTERED_CATEGORIES]: (state, { payload: filteredCategories }) => {
|
||||
return state.merge({
|
||||
'filteredCategories': filteredCategories,
|
||||
'areFeedsFiltered': true
|
||||
})
|
||||
},
|
||||
|
||||
[CLEAR_FILTERED_CATEGORIES]: (state, { payload }) => {
|
||||
return state.merge({
|
||||
'filteredCategories': [],
|
||||
'areFeedsFiltered': false
|
||||
})
|
||||
},
|
||||
|
||||
[SET_CATEGORIES]: (state, { payload: changedCategories }) => {
|
||||
return state.set('categories', changedCategories)
|
||||
},
|
||||
|
||||
[SHOW_DELETE_POPUP]: showPopup('delete'),
|
||||
|
||||
[HIDE_DELETE_POPUP]: hidePopup('delete'),
|
||||
|
||||
[SHOW_RENAME_POPUP]: showPopup('rename'),
|
||||
|
||||
[HIDE_RENAME_POPUP]: hidePopup('rename'),
|
||||
|
||||
[SHOW_ADD_CATEGORY_POPUP]: showPopup('addCategory'),
|
||||
|
||||
[HIDE_ADD_CATEGORY_POPUP]: hidePopup('addCategory'),
|
||||
|
||||
[SHOW_ADD_CLIPPINGS_POPUP]: showPopup('addClippingsFeed'),
|
||||
|
||||
[HIDE_ADD_CLIPPINGS_POPUP]: hidePopup('addClippingsFeed')
|
||||
|
||||
}, initialState)
|
||||
@@ -0,0 +1,594 @@
|
||||
import { createAction, handleActions } from 'redux-actions'
|
||||
import { fromJS } from 'immutable'
|
||||
import {thunkAction} from '../../utils/common'
|
||||
import * as api from '../../../api/searchApi'
|
||||
import { addAlert } from '../common/alerts'
|
||||
import {filtersFromServerFormat, ADV_FILTERS_LIMIT} from '../../utils/helpers/advancedFilters'
|
||||
|
||||
/*
|
||||
* Constants
|
||||
* */
|
||||
export const GET_SOURCE_INDEXES = 'GET_SOURCE_INDEXES'
|
||||
|
||||
export const SET_SOURCE_INDEX_SEARCH_QUERY = 'SET_SOURCE_INDEX_SEARCH_QUERY'
|
||||
|
||||
export const TOGGLE_SOURCE_INDEX = 'TOGGLE_SOURCE_INDEX'
|
||||
export const TOGGLE_ALL_SOURCE_INDEXES = 'TOGGLE_ALL_SOURCE_INDEXES'
|
||||
|
||||
export const GET_MAIN_SOURCE_LISTS = 'GET_MAIN_SOURCE_LISTS'
|
||||
export const TOGGLE_ONLY_GLOBAL = 'TOGGLE_ONLY_GLOBAL'
|
||||
|
||||
export const TOGGLE_ADD_SOURCE_TO_LIST_POPUP = 'TOGGLE_ADD_SOURCE_TO_LIST_POPUP'
|
||||
export const ADD_SOURCE_LIST = 'ADD_SOURCE_LIST'
|
||||
export const DELETE_SOURCE_LIST = 'DELETE_SOURCE_LIST'
|
||||
export const RENAME_SOURCE_LIST = 'RENAME_SOURCE_LIST'
|
||||
export const CLONE_SOURCE_LIST = 'CLONE_SOURCE_LIST'
|
||||
export const ADD_SOURCES_TO_LIST = 'ADD_SOURCES_TO_LIST'
|
||||
export const UPDATE_LIST_SOURCES = 'UPDATE_LIST_SOURCES'
|
||||
export const SET_CHOSEN_LISTS_TO_ADD_SOURCES = 'SET_CHOSEN_LISTS_TO_ADD_SOURCES'
|
||||
|
||||
export const SHOW_UPDATE_SOURCE_POPUP = 'SHOW_UPDATE_SOURCE_POPUP'
|
||||
export const HIDE_UPDATE_SOURCE_POPUP = 'HIDE_UPDATE_SOURCE_POPUP'
|
||||
export const SET_CHOSEN_LISTS_TO_UPDATE_SOURCES = 'SET_CHOSEN_LISTS_TO_UPDATE_SOURCES'
|
||||
|
||||
export const TOGGLE_ADD_LIST_POPUP = 'TOGGLE_ADD_LIST_POPUP'
|
||||
|
||||
export const TOGGLE_DELETE_LIST_POPUP = 'TOGGLE_DELETE_LIST_POPUP'
|
||||
|
||||
export const TOGGLE_RENAME_LIST_POPUP = 'TOGGLE_RENAME_LIST_POPUP'
|
||||
|
||||
export const TOGGLE_CLONE_LIST_POPUP = 'TOGGLE_CLONE_LIST_POPUP'
|
||||
|
||||
export const TOGGLE_SOURCE_INFO_POPUP = 'TOGGLE_SOURCE_INFO_POPUP'
|
||||
|
||||
export const GET_SOURCES_OF_LIST = 'GET_SOURCES_OF_LIST'
|
||||
export const SHOW_SOURCES_OF_LIST = 'SHOW_SOURCES_OF_LIST'
|
||||
export const HIDE_SOURCES_OF_LIST = 'HIDE_SOURCES_OF_LIST'
|
||||
export const SET_SOURCES_OF_LIST_SEARCH_QUERY = 'SET_SOURCES_OF_LIST_SEARCH_QUERY'
|
||||
|
||||
const SELECT_SOURCES_FILTER = 'SELECT_SOURCES_FILTER'
|
||||
const CLEAR_SOURCES_FILTERS = 'CLEAR_SOURCES_FILTERS'
|
||||
const CLEAR_ALL_SOURCES_FILTERS = 'CLEAR_ALL_SOURCES_FILTERS'
|
||||
const LOAD_MORE_SOURCES_FILTERS = 'LOAD_MORE_SOURCES_FILTERS'
|
||||
const LOAD_LESS_SOURCES_FILTERS = 'LOAD_LESS_SOURCES_FILTERS'
|
||||
|
||||
const SHARE_SOURCE_LIST = 'SHARE_SOURCE_LIST'
|
||||
const UNSHARE_SOURCE_LIST = 'UNSHARE_SOURCE_LIST'
|
||||
|
||||
/*
|
||||
* Actions
|
||||
* */
|
||||
const getSourceIndexes = thunkAction(GET_SOURCE_INDEXES, (params, {token, getState, fulfilled}) => {
|
||||
const sourceIndexesState = getState().getIn(['appState', 'sourcesState', 'sourceIndexesState'])
|
||||
const query = sourceIndexesState.get('searchQuery')
|
||||
const page = sourceIndexesState.get('page')
|
||||
const limit = sourceIndexesState.get('limit')
|
||||
const selectedFilters = sourceIndexesState.getIn(['advancedFilters', 'selected'])
|
||||
let dataToSend = {
|
||||
query,
|
||||
page,
|
||||
limit
|
||||
}
|
||||
if (Object.keys(selectedFilters).length > 0) {
|
||||
dataToSend.advancedFilters = selectedFilters
|
||||
}
|
||||
if (params) {
|
||||
Object.assign(dataToSend, params)
|
||||
}
|
||||
|
||||
return api.searchSources(token, dataToSend)
|
||||
.then((response) => fulfilled(response))
|
||||
}, true)
|
||||
|
||||
export const setSourceIndexSearchQuery = createAction(SET_SOURCE_INDEX_SEARCH_QUERY, (query) => query)
|
||||
|
||||
export const toggleSourceIndex = createAction(TOGGLE_SOURCE_INDEX, itemId => itemId)
|
||||
export const toggleAllSourceIndexes = createAction(TOGGLE_ALL_SOURCE_INDEXES, isChosen => isChosen)
|
||||
|
||||
export const getMainSourceLists = thunkAction(GET_MAIN_SOURCE_LISTS, (dataToSend, {token, fulfilled}) => {
|
||||
return api.getSourceLists(token, dataToSend)
|
||||
.then((response) => fulfilled(response))
|
||||
}, true)
|
||||
|
||||
export const toggleOnlyGlobal = createAction(TOGGLE_ONLY_GLOBAL)
|
||||
|
||||
export const toggleAddSourceToListPopup = createAction(TOGGLE_ADD_SOURCE_TO_LIST_POPUP)
|
||||
|
||||
export const setChosenListsToAddSources = createAction(SET_CHOSEN_LISTS_TO_ADD_SOURCES, (newLists) => newLists)
|
||||
|
||||
const addSourcesToList = thunkAction(ADD_SOURCES_TO_LIST, (dataToSend, isAdd, {token, dispatch, getState}) => {
|
||||
return api.addSourcesToLists(token, dataToSend)
|
||||
.then(() => {
|
||||
dispatch(getSourceIndexes(null))
|
||||
dispatch(addAlert({
|
||||
type: 'notice',
|
||||
transKey: 'updateListsForSourceNotice',
|
||||
id: 'updateListsForSourceNotice'
|
||||
}))
|
||||
isAdd
|
||||
? dispatch(toggleAddSourceToListPopup())
|
||||
: dispatch(hideUpdateSourcePopup())
|
||||
})
|
||||
})
|
||||
|
||||
const addSourceList = thunkAction(ADD_SOURCE_LIST, (name, {token, dispatch}) => {
|
||||
return api.addSourceLists(token, name)
|
||||
.then(() => {
|
||||
dispatch(getMainSourceLists({}))
|
||||
dispatch(addAlert({
|
||||
type: 'notice',
|
||||
transKey: 'addSourceList',
|
||||
parameters: {
|
||||
name
|
||||
}
|
||||
}))
|
||||
dispatch(toggleAddListPopup())
|
||||
})
|
||||
})
|
||||
|
||||
const deleteSourceList = thunkAction(DELETE_SOURCE_LIST, (data, {token, dispatch}) => {
|
||||
return api.deleteSourceLists(token, data.id)
|
||||
.then(() => {
|
||||
dispatch(getMainSourceLists({}))
|
||||
|
||||
dispatch(toggleDeleteListPopup())
|
||||
|
||||
dispatch(addAlert({
|
||||
type: 'notice',
|
||||
transKey: 'deleteSourceList',
|
||||
parameters: {name: data.name}
|
||||
}))
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
const renameSourceList = thunkAction(RENAME_SOURCE_LIST, (data, oldName, {token, dispatch}) => {
|
||||
return api.renameSourceLists(token, data)
|
||||
.then(() => {
|
||||
dispatch(getMainSourceLists({}))
|
||||
dispatch(addAlert({
|
||||
type: 'notice',
|
||||
transKey: 'renameSourceList',
|
||||
parameters: oldName
|
||||
}))
|
||||
dispatch(toggleRenameListPopup())
|
||||
})
|
||||
})
|
||||
|
||||
const cloneSourceList = thunkAction(CLONE_SOURCE_LIST, (data, {token, dispatch}) => {
|
||||
return api.cloneSourceLists(token, data)
|
||||
.then(() => {
|
||||
dispatch(getMainSourceLists({}))
|
||||
dispatch(addAlert({
|
||||
type: 'notice',
|
||||
transKey: 'cloneSourceList'
|
||||
}))
|
||||
dispatch(toggleCloneListPopup())
|
||||
})
|
||||
})
|
||||
|
||||
const updateListSources = thunkAction(UPDATE_LIST_SOURCES, (dataToSend, {token, dispatch, getState}) => {
|
||||
return api.replaceSourceListsForSource(token, dataToSend)
|
||||
.then(() => {
|
||||
dispatch(addAlert({
|
||||
type: 'notice',
|
||||
transKey: 'updateListsForSourceNotice',
|
||||
id: 'updateListsForSourceNotice'
|
||||
}))
|
||||
|
||||
const sourcesOfListState = getState().getIn(['appState', 'sourcesState', 'sourcesOfListState'])
|
||||
const query = sourcesOfListState.get('searchQuery')
|
||||
const page = sourcesOfListState.get('page')
|
||||
const limit = sourcesOfListState.get('limit')
|
||||
const listId = sourcesOfListState.getIn(['visibleList', 'id'])
|
||||
dispatch(getSourcesOfList(listId, {query, page, limit}))
|
||||
})
|
||||
})
|
||||
|
||||
const shareSourceList = thunkAction(SHARE_SOURCE_LIST, (id, {token, dispatch}) => {
|
||||
return api.shareSourceList(token, id)
|
||||
.then(() => {
|
||||
dispatch(getMainSourceLists({}))
|
||||
dispatch(addAlert({
|
||||
type: 'notice',
|
||||
transKey: 'shareSourceList'
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
const unshareSourceList = thunkAction(UNSHARE_SOURCE_LIST, (id, {token, dispatch}) => {
|
||||
return api.unshareSourceList(token, id)
|
||||
.then(() => {
|
||||
dispatch(getMainSourceLists({}))
|
||||
dispatch(addAlert({
|
||||
type: 'notice',
|
||||
transKey: 'unshareSourceList'
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
export const showUpdateSourcePopup = createAction(SHOW_UPDATE_SOURCE_POPUP, (chosenSource) => chosenSource)
|
||||
export const hideUpdateSourcePopup = createAction(HIDE_UPDATE_SOURCE_POPUP)
|
||||
export const setChosenListsToUpdateSources = createAction(SET_CHOSEN_LISTS_TO_UPDATE_SOURCES, (newLists) => newLists)
|
||||
|
||||
export const toggleAddListPopup = createAction(TOGGLE_ADD_LIST_POPUP)
|
||||
|
||||
export const _toggleDeletePopup = createAction(TOGGLE_DELETE_LIST_POPUP, (type, list) => ({type, list}))
|
||||
|
||||
export const toggleDeleteListPopup = (list) => (dispatch) => {
|
||||
dispatch(_toggleDeletePopup('sourceListsState', list))
|
||||
}
|
||||
export const toggleDeleteListIndexPopup = (list) => (dispatch) => {
|
||||
dispatch(_toggleDeletePopup('sourcesOfListState', list))
|
||||
}
|
||||
|
||||
export const toggleRenameListPopup = createAction(TOGGLE_RENAME_LIST_POPUP, (list) => list)
|
||||
|
||||
export const toggleCloneListPopup = createAction(TOGGLE_CLONE_LIST_POPUP, (list) => list)
|
||||
|
||||
const toggleInfoSourcePopup = createAction(TOGGLE_SOURCE_INFO_POPUP, (type, item) => ({type, item}))
|
||||
|
||||
export const getSourcesOfList = thunkAction(GET_SOURCES_OF_LIST, (id, dataToSend, {token, fulfilled}) => {
|
||||
return api
|
||||
.getSourcesOfList(token, dataToSend, id)
|
||||
.then((response) => fulfilled(response))
|
||||
}, true)
|
||||
|
||||
export const showSourcesOfList = createAction(SHOW_SOURCES_OF_LIST, (list) => list)
|
||||
|
||||
export const hideSourcesOfList = createAction(HIDE_SOURCES_OF_LIST)
|
||||
|
||||
export const setSourcesOfListSearchQuery = createAction(SET_SOURCES_OF_LIST_SEARCH_QUERY, (query) => query)
|
||||
|
||||
export const selectSourcesFilter = createAction(SELECT_SOURCES_FILTER, (groupName, filterValue) => { return {groupName, filterValue} })
|
||||
export const clearSourcesFilters = createAction(CLEAR_SOURCES_FILTERS)
|
||||
export const clearAllSourcesFilters = createAction(CLEAR_ALL_SOURCES_FILTERS)
|
||||
export const loadMoreSourcesFilters = createAction(LOAD_MORE_SOURCES_FILTERS)
|
||||
export const loadLessSourcesFilters = createAction(LOAD_LESS_SOURCES_FILTERS)
|
||||
|
||||
export const actions = {
|
||||
getSourceIndexes,
|
||||
setSourceIndexSearchQuery,
|
||||
toggleSourceIndex,
|
||||
toggleAllSourceIndexes,
|
||||
getMainSourceLists,
|
||||
toggleOnlyGlobal,
|
||||
toggleAddSourceToListPopup,
|
||||
addSourcesToList,
|
||||
updateListSources,
|
||||
setChosenListsToAddSources,
|
||||
showUpdateSourcePopup,
|
||||
hideUpdateSourcePopup,
|
||||
setChosenListsToUpdateSources,
|
||||
toggleInfoSourcePopup,
|
||||
toggleAddListPopup,
|
||||
toggleDeleteListPopup,
|
||||
toggleDeleteListIndexPopup,
|
||||
toggleRenameListPopup,
|
||||
toggleCloneListPopup,
|
||||
getSourcesOfList,
|
||||
showSourcesOfList,
|
||||
hideSourcesOfList,
|
||||
setSourcesOfListSearchQuery,
|
||||
selectSourcesFilter,
|
||||
clearSourcesFilters,
|
||||
clearAllSourcesFilters,
|
||||
loadMoreSourcesFilters,
|
||||
loadLessSourcesFilters,
|
||||
addSourceList,
|
||||
deleteSourceList,
|
||||
renameSourceList,
|
||||
cloneSourceList,
|
||||
shareSourceList,
|
||||
unshareSourceList
|
||||
}
|
||||
|
||||
/*
|
||||
* State
|
||||
* */
|
||||
export const initialState = fromJS({
|
||||
sourceIndexesState: {
|
||||
searchQuery: '',
|
||||
page: 1,
|
||||
limit: 25,
|
||||
sortByField: 'id',
|
||||
sortDirection: 'asc',
|
||||
data: [],
|
||||
count: 0,
|
||||
totalCount: 0,
|
||||
isLoading: false,
|
||||
isAddPopupVisible: false,
|
||||
isUpdatePopupVisible: false,
|
||||
infoPopup: {
|
||||
visible: false,
|
||||
item: null
|
||||
},
|
||||
chosenListsToAddSources: [],
|
||||
chosenSourceToUpdate: {}, // source id on which we click to add / remove it.
|
||||
idsToDelete: [],
|
||||
selectedIds: [], //map of ids of items that selected in table
|
||||
isAllSelected: false,
|
||||
advancedFilters: {
|
||||
all: {},
|
||||
pages: {}, //{groupName1: {count: xx, totalCount: yy}, groupName2: ....}
|
||||
selected: {}, // {groupName1: {value1: 0, value2: 1, ....}, groupName2: {}, ... } will be send to the server
|
||||
pending: {} //which groups is not applied yet
|
||||
}
|
||||
},
|
||||
sourceListsState: {
|
||||
page: 1,
|
||||
limit: 25,
|
||||
sortByField: 'id',
|
||||
sortDirection: 'asc',
|
||||
data: [],
|
||||
count: 0,
|
||||
totalCount: 0,
|
||||
onlyGlobal: false,
|
||||
isLoading: false,
|
||||
isAddListPopupVisible: false,
|
||||
isDeletePopupVisible: false,
|
||||
isRenameListPopupVisible: false,
|
||||
isCloneListPopupVisible: false,
|
||||
listToEdit: {}
|
||||
},
|
||||
sourcesOfListState: {
|
||||
isSourcesOfListVisible: false,
|
||||
visibleList: null,
|
||||
searchQuery: '',
|
||||
page: 1,
|
||||
limit: 25,
|
||||
sortByField: 'id',
|
||||
sortDirection: 'asc',
|
||||
data: [],
|
||||
count: 0,
|
||||
totalCount: 0,
|
||||
infoPopup: {
|
||||
visible: false,
|
||||
item: null
|
||||
},
|
||||
isDeletePopupVisible: false,
|
||||
isLoading: false,
|
||||
listToEdit: {}
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
* Reducers
|
||||
* */
|
||||
export default handleActions({
|
||||
|
||||
[`${GET_SOURCE_INDEXES} pending`]: (state, { payload }) => {
|
||||
return state.setIn(['sourceIndexesState', 'isLoading'], payload.isPending)
|
||||
},
|
||||
|
||||
[`${GET_SOURCE_INDEXES} fulfilled`]: (state, { payload: {sources, advancedFilters, meta} }) => {
|
||||
|
||||
const {allFilters, pages} = filtersFromServerFormat(advancedFilters)
|
||||
|
||||
return state.mergeIn(['sourceIndexesState'], {
|
||||
'data': sources.data,
|
||||
'isLoading': false,
|
||||
'page': sources.page,
|
||||
'limit': sources.limit,
|
||||
'count': sources.count,
|
||||
'totalCount': sources.totalCount,
|
||||
'sortByField': meta.sort.field || 'name',
|
||||
'sortDirection': meta.sort.direction || 'asc'
|
||||
}).mergeIn(['sourceIndexesState', 'advancedFilters'], {
|
||||
all: allFilters,
|
||||
pages: pages,
|
||||
selected: meta.advancedFilters,
|
||||
pending: {}
|
||||
})
|
||||
},
|
||||
|
||||
[SET_SOURCE_INDEX_SEARCH_QUERY]: (state, {payload: query}) => {
|
||||
return state.setIn(['sourceIndexesState', 'searchQuery'], query)
|
||||
},
|
||||
|
||||
[TOGGLE_SOURCE_INDEX]: (state, {payload: itemId}) => {
|
||||
const path = ['sourceIndexesState', 'selectedIds']
|
||||
|
||||
let selectedIds = state.getIn(path)
|
||||
const isSelected = selectedIds.includes(itemId)
|
||||
if (isSelected) {
|
||||
selectedIds = selectedIds.filter(id => id !== itemId)
|
||||
}
|
||||
else {
|
||||
selectedIds = selectedIds.push(itemId)
|
||||
}
|
||||
return state.setIn(path, selectedIds)
|
||||
},
|
||||
|
||||
[TOGGLE_ALL_SOURCE_INDEXES]: (state) => {
|
||||
const type = 'sourceIndexesState'
|
||||
const isAllSelected = state.getIn([type, 'isAllSelected'])
|
||||
if (isAllSelected) { //then deselect all
|
||||
return state.mergeIn([type], {
|
||||
isAllSelected: false,
|
||||
selectedIds: []
|
||||
})
|
||||
}
|
||||
else { //select all currently loaded data
|
||||
const selectedIds = state.getIn([type, 'data']).map(item => item.get('id'))
|
||||
return state.mergeIn([type], {
|
||||
isAllSelected: true,
|
||||
selectedIds
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
[`${GET_MAIN_SOURCE_LISTS} pending`]: (state, { payload }) => {
|
||||
return state.setIn(['sourceListsState', 'isLoading'], payload.isPending)
|
||||
},
|
||||
|
||||
[`${GET_MAIN_SOURCE_LISTS} fulfilled`]: (state, { payload }) => {
|
||||
const response = payload.data
|
||||
return state.mergeIn(['sourceListsState'], {
|
||||
'data': response,
|
||||
'isLoading': false,
|
||||
'page': payload.page,
|
||||
'limit': payload.limit,
|
||||
'count': payload.count,
|
||||
'totalCount': payload.totalCount,
|
||||
'sortByField': payload.sort.field || 'name',
|
||||
'sortDirection': payload.sort.direction || 'asc'
|
||||
})
|
||||
},
|
||||
|
||||
[TOGGLE_ONLY_GLOBAL]: (state) => {
|
||||
const onlyGlobal = state.getIn(['sourceListsState', 'onlyGlobal'])
|
||||
return state.setIn(['sourceListsState', 'onlyGlobal'], !onlyGlobal)
|
||||
},
|
||||
|
||||
[TOGGLE_ADD_SOURCE_TO_LIST_POPUP]: (state, { payload }) => {
|
||||
const isVisible = !state.getIn(['sourceIndexesState', 'isAddPopupVisible'])
|
||||
return state.mergeIn(['sourceIndexesState'], {
|
||||
'isAddPopupVisible': isVisible,
|
||||
'chosenListsToAddSources': []
|
||||
})
|
||||
},
|
||||
|
||||
[SET_CHOSEN_LISTS_TO_ADD_SOURCES]: (state, {payload: newSources}) => {
|
||||
return state.setIn(['sourceIndexesState', 'chosenListsToAddSources'], newSources)
|
||||
},
|
||||
|
||||
[SHOW_UPDATE_SOURCE_POPUP]: (state, { payload: chosenSource }) => {
|
||||
return state.mergeIn(['sourceIndexesState'], {
|
||||
'isUpdatePopupVisible': true,
|
||||
'chosenSourceToUpdate': chosenSource
|
||||
})
|
||||
},
|
||||
|
||||
[HIDE_UPDATE_SOURCE_POPUP]: (state, { payload }) => {
|
||||
return state.mergeIn(['sourceIndexesState'], {
|
||||
'isUpdatePopupVisible': false,
|
||||
'chosenSourceToUpdate': {}
|
||||
})
|
||||
},
|
||||
|
||||
[SET_CHOSEN_LISTS_TO_UPDATE_SOURCES]: (state, {payload: newSources}) => {
|
||||
return state.setIn(['sourceIndexesState', 'chosenSourceToUpdate', 'listIds'], newSources)
|
||||
},
|
||||
|
||||
[TOGGLE_ADD_LIST_POPUP]: (state, {payload}) => {
|
||||
const isVisible = !state.getIn(['sourceListsState', 'isAddListPopupVisible'])
|
||||
return state.setIn(['sourceListsState', 'isAddListPopupVisible'], isVisible)
|
||||
},
|
||||
|
||||
[TOGGLE_DELETE_LIST_POPUP]: (state, {payload}) => {
|
||||
const { type, list } = payload
|
||||
const isVisible = !state.getIn([type, 'isDeletePopupVisible'])
|
||||
return state.mergeIn([type], {
|
||||
'isDeletePopupVisible': isVisible,
|
||||
'listToEdit': isVisible ? list : {}
|
||||
})
|
||||
},
|
||||
|
||||
[TOGGLE_RENAME_LIST_POPUP]: (state, {payload: list}) => {
|
||||
const isVisible = !state.getIn(['sourceListsState', 'isRenameListPopupVisible'])
|
||||
return state.mergeIn(['sourceListsState'], {
|
||||
'isRenameListPopupVisible': isVisible,
|
||||
'listToEdit': isVisible ? list : {}
|
||||
})
|
||||
},
|
||||
|
||||
[TOGGLE_CLONE_LIST_POPUP]: (state, {payload: list}) => {
|
||||
const isVisible = !state.getIn(['sourceListsState', 'isCloneListPopupVisible'])
|
||||
return state.mergeIn(['sourceListsState'], {
|
||||
'isCloneListPopupVisible': isVisible,
|
||||
'listToEdit': isVisible ? list : {}
|
||||
})
|
||||
},
|
||||
|
||||
[TOGGLE_SOURCE_INFO_POPUP]: (state, {payload}) => {
|
||||
const { type, item } = payload
|
||||
const popupPath = [type, 'infoPopup']
|
||||
const isVisible = !state.getIn(popupPath.concat('visible'))
|
||||
return state.mergeIn(popupPath, {
|
||||
visible: isVisible,
|
||||
item: isVisible ? item : null
|
||||
})
|
||||
},
|
||||
|
||||
[`${GET_SOURCES_OF_LIST} pending`]: (state, { payload }) => {
|
||||
return state.setIn(['sourcesOfListState', 'isLoading'], payload.isPending)
|
||||
},
|
||||
|
||||
[`${GET_SOURCES_OF_LIST} fulfilled`]: (state, { payload }) => {
|
||||
const sources = payload.sources
|
||||
return state.mergeIn(['sourcesOfListState'], {
|
||||
'data': sources.data,
|
||||
'isLoading': false,
|
||||
'page': sources.page,
|
||||
'limit': sources.limit,
|
||||
'count': sources.count,
|
||||
'totalCount': sources.totalCount,
|
||||
'sortByField': payload.sort.field || 'name',
|
||||
'sortDirection': payload.sort.direction || 'asc'
|
||||
})
|
||||
},
|
||||
|
||||
[SHOW_SOURCES_OF_LIST]: (state, {payload: list}) => {
|
||||
return state.mergeIn(['sourcesOfListState'], {
|
||||
'isSourcesOfListVisible': true,
|
||||
'visibleList': list
|
||||
})
|
||||
},
|
||||
|
||||
[HIDE_SOURCES_OF_LIST]: (state, {payload}) => {
|
||||
return state.mergeIn(['sourcesOfListState'], {
|
||||
'isSourcesOfListVisible': false,
|
||||
'visibleList': {},
|
||||
'data': []
|
||||
})
|
||||
},
|
||||
|
||||
[SET_SOURCES_OF_LIST_SEARCH_QUERY]: (state, {payload: query}) => {
|
||||
return state.setIn(['sourcesOfListState', 'searchQuery'], query)
|
||||
},
|
||||
|
||||
[SELECT_SOURCES_FILTER]: (state, {payload: {groupName, filterValue}}) => {
|
||||
const basePath = ['sourceIndexesState', 'advancedFilters']
|
||||
const selectionPath = [...basePath, 'selected', groupName]
|
||||
if (groupName === 'articleDate') {
|
||||
state = state.deleteIn(selectionPath)
|
||||
}
|
||||
//tri-state switch
|
||||
const currentState = state.getIn([...selectionPath, filterValue])
|
||||
let newState
|
||||
if (currentState === undefined) {
|
||||
newState = 1
|
||||
} else if (currentState === 1) {
|
||||
newState = -1
|
||||
}
|
||||
return state.deleteIn([...basePath, 'pending', groupName]).setIn([...selectionPath, filterValue], newState)
|
||||
},
|
||||
|
||||
[CLEAR_SOURCES_FILTERS]: (state, {payload: groupName}) => {
|
||||
const basePath = ['sourceIndexesState', 'advancedFilters']
|
||||
return state.setIn([...basePath, 'pending', groupName], true).deleteIn([...basePath, 'selected', groupName])
|
||||
},
|
||||
|
||||
[CLEAR_ALL_SOURCES_FILTERS]: (state) => {
|
||||
return state.mergeIn(['sourceIndexesState', 'advancedFilters'], {
|
||||
selected: {},
|
||||
pending: {}
|
||||
})
|
||||
},
|
||||
|
||||
[LOAD_LESS_SOURCES_FILTERS]: (state, {payload: groupName}) => {
|
||||
const path = ['sourceIndexesState', 'advancedFilters', 'pages', groupName, 'count']
|
||||
const currentCount = state.getIn(path)
|
||||
return state.setIn(path, Math.max(currentCount - ADV_FILTERS_LIMIT, ADV_FILTERS_LIMIT))
|
||||
},
|
||||
|
||||
[LOAD_MORE_SOURCES_FILTERS]: (state, {payload: groupName}) => {
|
||||
const path = ['sourceIndexesState', 'advancedFilters', 'pages', groupName]
|
||||
const currentCount = state.getIn([...path, 'count'])
|
||||
console.log(currentCount)
|
||||
const totalCount = state.getIn([...path, 'totalCount'])
|
||||
return state.setIn([...path, 'count'], Math.min(currentCount + ADV_FILTERS_LIMIT, totalCount))
|
||||
}
|
||||
|
||||
}, initialState)
|
||||
@@ -0,0 +1,265 @@
|
||||
import sideBar6 from '../../../styles/utils/images/sidebar/city1.jpg'
|
||||
|
||||
export const SET_ENABLE_BACKGROUND_IMAGE =
|
||||
'THEME_OPTIONS/SET_ENABLE_BACKGROUND_IMAGE'
|
||||
|
||||
export const SET_ENABLE_MOBILE_MENU = 'THEME_OPTIONS/SET_ENABLE_MOBILE_MENU'
|
||||
export const SET_ENABLE_MOBILE_MENU_SMALL =
|
||||
'THEME_OPTIONS/SET_ENABLE_MOBILE_MENU_SMALL'
|
||||
|
||||
export const SET_ENABLE_FIXED_HEADER = 'THEME_OPTIONS/SET_ENABLE_FIXED_HEADER'
|
||||
export const SET_ENABLE_HEADER_SHADOW = 'THEME_OPTIONS/SET_ENABLE_HEADER_SHADOW'
|
||||
export const SET_ENABLE_SIDEBAR_SHADOW =
|
||||
'THEME_OPTIONS/SET_ENABLE_SIDEBAR_SHADOW'
|
||||
export const SET_ENABLE_FIXED_SIDEBAR = 'THEME_OPTIONS/SET_ENABLE_FIXED_SIDEBAR'
|
||||
export const SET_ENABLE_CLOSED_SIDEBAR =
|
||||
'THEME_OPTIONS/SET_ENABLE_CLOSED_SIDEBAR'
|
||||
export const SET_ENABLE_FIXED_FOOTER = 'THEME_OPTIONS/SET_ENABLE_FIXED_FOOTER'
|
||||
|
||||
export const SET_ENABLE_PAGETITLE_ICON =
|
||||
'THEME_OPTIONS/SET_ENABLE_PAGETITLE_ICON'
|
||||
export const SET_ENABLE_PAGETITLE_SUBHEADING =
|
||||
'THEME_OPTIONS/SET_ENABLE_PAGETITLE_SUBHEADING'
|
||||
export const SET_ENABLE_PAGE_TABS_ALT = 'THEME_OPTIONS/SET_ENABLE_PAGE_TABS_ALT'
|
||||
|
||||
export const SET_BACKGROUND_IMAGE = 'THEME_OPTIONS/SET_BACKGROUND_IMAGE'
|
||||
export const SET_BACKGROUND_COLOR = 'THEME_OPTIONS/SET_BACKGROUND_COLOR'
|
||||
export const SET_COLOR_SCHEME = 'THEME_OPTIONS/SET_COLOR_SCHEME'
|
||||
export const SET_BACKGROUND_IMAGE_OPACITY =
|
||||
'THEME_OPTIONS/SET_BACKGROUND_IMAGE_OPACITY'
|
||||
|
||||
export const SET_HEADER_BACKGROUND_COLOR =
|
||||
'THEME_OPTIONS/SET_HEADER_BACKGROUND_COLOR'
|
||||
|
||||
const setEnableBackgroundImage = (enableBackgroundImage) => ({
|
||||
type: SET_ENABLE_BACKGROUND_IMAGE,
|
||||
enableBackgroundImage
|
||||
})
|
||||
|
||||
const setEnableFixedHeader = (enableFixedHeader) => ({
|
||||
type: SET_ENABLE_FIXED_HEADER,
|
||||
enableFixedHeader
|
||||
})
|
||||
|
||||
const setEnableHeaderShadow = (enableHeaderShadow) => ({
|
||||
type: SET_ENABLE_HEADER_SHADOW,
|
||||
enableHeaderShadow
|
||||
})
|
||||
|
||||
const setEnableSidebarShadow = (enableSidebarShadow) => ({
|
||||
type: SET_ENABLE_SIDEBAR_SHADOW,
|
||||
enableSidebarShadow
|
||||
})
|
||||
|
||||
const setEnablePageTitleIcon = (enablePageTitleIcon) => ({
|
||||
type: SET_ENABLE_PAGETITLE_ICON,
|
||||
enablePageTitleIcon
|
||||
})
|
||||
|
||||
const setEnablePageTitleSubheading = (enablePageTitleSubheading) => ({
|
||||
type: SET_ENABLE_PAGETITLE_SUBHEADING,
|
||||
enablePageTitleSubheading
|
||||
})
|
||||
|
||||
const setEnablePageTabsAlt = (enablePageTabsAlt) => ({
|
||||
type: SET_ENABLE_PAGE_TABS_ALT,
|
||||
enablePageTabsAlt
|
||||
})
|
||||
|
||||
const setEnableFixedSidebar = (enableFixedSidebar) => ({
|
||||
type: SET_ENABLE_FIXED_SIDEBAR,
|
||||
enableFixedSidebar
|
||||
})
|
||||
|
||||
const setEnableClosedSidebar = (enableClosedSidebar) => ({
|
||||
type: SET_ENABLE_CLOSED_SIDEBAR,
|
||||
enableClosedSidebar
|
||||
})
|
||||
|
||||
const setEnableMobileMenu = (enableMobileMenu) => ({
|
||||
type: SET_ENABLE_MOBILE_MENU,
|
||||
enableMobileMenu
|
||||
})
|
||||
|
||||
const setEnableMobileMenuSmall = (enableMobileMenuSmall) => ({
|
||||
type: SET_ENABLE_MOBILE_MENU_SMALL,
|
||||
enableMobileMenuSmall
|
||||
})
|
||||
|
||||
const setEnableFixedFooter = (enableFixedFooter) => ({
|
||||
type: SET_ENABLE_FIXED_FOOTER,
|
||||
enableFixedFooter
|
||||
})
|
||||
|
||||
const setBackgroundColor = (backgroundColor) => ({
|
||||
type: SET_BACKGROUND_COLOR,
|
||||
backgroundColor
|
||||
})
|
||||
|
||||
const setHeaderBackgroundColor = (headerBackgroundColor) => ({
|
||||
type: SET_HEADER_BACKGROUND_COLOR,
|
||||
headerBackgroundColor
|
||||
})
|
||||
|
||||
const setColorScheme = (colorScheme) => ({
|
||||
type: SET_COLOR_SCHEME,
|
||||
colorScheme
|
||||
})
|
||||
|
||||
const setBackgroundImageOpacity = (backgroundImageOpacity) => ({
|
||||
type: SET_BACKGROUND_IMAGE_OPACITY,
|
||||
backgroundImageOpacity
|
||||
})
|
||||
|
||||
const setBackgroundImage = (backgroundImage) => ({
|
||||
type: SET_BACKGROUND_IMAGE,
|
||||
backgroundImage
|
||||
})
|
||||
|
||||
export const themeActions = {
|
||||
setEnableBackgroundImage,
|
||||
setEnableFixedHeader,
|
||||
setEnableHeaderShadow,
|
||||
setEnableSidebarShadow,
|
||||
setEnablePageTitleIcon,
|
||||
setEnablePageTitleSubheading,
|
||||
setEnablePageTabsAlt,
|
||||
setEnableFixedSidebar,
|
||||
setEnableClosedSidebar,
|
||||
setEnableMobileMenu,
|
||||
setEnableMobileMenuSmall,
|
||||
setEnableFixedFooter,
|
||||
setBackgroundColor,
|
||||
setHeaderBackgroundColor,
|
||||
setColorScheme,
|
||||
setBackgroundImageOpacity,
|
||||
setBackgroundImage
|
||||
}
|
||||
|
||||
export default function ThemeOptions (
|
||||
state = {
|
||||
backgroundColor: '',
|
||||
headerBackgroundColor: '',
|
||||
enableMobileMenuSmall: '',
|
||||
enableBackgroundImage: false,
|
||||
enableClosedSidebar: false,
|
||||
enableFixedHeader: true,
|
||||
enableHeaderShadow: true,
|
||||
enableSidebarShadow: true,
|
||||
enableFixedFooter: true,
|
||||
enableFixedSidebar: true,
|
||||
colorScheme: 'white',
|
||||
backgroundImage: sideBar6,
|
||||
backgroundImageOpacity: 'opacity-06',
|
||||
enablePageTitleIcon: true,
|
||||
enablePageTitleSubheading: true,
|
||||
enablePageTabsAlt: true
|
||||
},
|
||||
action
|
||||
) {
|
||||
switch (action.type) {
|
||||
case SET_ENABLE_BACKGROUND_IMAGE:
|
||||
return {
|
||||
...state,
|
||||
enableBackgroundImage: action.enableBackgroundImage
|
||||
}
|
||||
|
||||
case SET_ENABLE_FIXED_HEADER:
|
||||
return {
|
||||
...state,
|
||||
enableFixedHeader: action.enableFixedHeader
|
||||
}
|
||||
|
||||
case SET_ENABLE_HEADER_SHADOW:
|
||||
return {
|
||||
...state,
|
||||
enableHeaderShadow: action.enableHeaderShadow
|
||||
}
|
||||
|
||||
case SET_ENABLE_SIDEBAR_SHADOW:
|
||||
return {
|
||||
...state,
|
||||
enableSidebarShadow: action.enableSidebarShadow
|
||||
}
|
||||
|
||||
case SET_ENABLE_PAGETITLE_ICON:
|
||||
return {
|
||||
...state,
|
||||
enablePageTitleIcon: action.enablePageTitleIcon
|
||||
}
|
||||
|
||||
case SET_ENABLE_PAGETITLE_SUBHEADING:
|
||||
return {
|
||||
...state,
|
||||
enablePageTitleSubheading: action.enablePageTitleSubheading
|
||||
}
|
||||
|
||||
case SET_ENABLE_PAGE_TABS_ALT:
|
||||
return {
|
||||
...state,
|
||||
enablePageTabsAlt: action.enablePageTabsAlt
|
||||
}
|
||||
|
||||
case SET_ENABLE_FIXED_SIDEBAR:
|
||||
return {
|
||||
...state,
|
||||
enableFixedSidebar: action.enableFixedSidebar
|
||||
}
|
||||
|
||||
case SET_ENABLE_MOBILE_MENU:
|
||||
return {
|
||||
...state,
|
||||
enableMobileMenu: action.enableMobileMenu
|
||||
}
|
||||
|
||||
case SET_ENABLE_MOBILE_MENU_SMALL:
|
||||
return {
|
||||
...state,
|
||||
enableMobileMenuSmall: action.enableMobileMenuSmall
|
||||
}
|
||||
|
||||
case SET_ENABLE_CLOSED_SIDEBAR:
|
||||
return {
|
||||
...state,
|
||||
enableClosedSidebar: action.enableClosedSidebar
|
||||
}
|
||||
|
||||
case SET_ENABLE_FIXED_FOOTER:
|
||||
return {
|
||||
...state,
|
||||
enableFixedFooter: action.enableFixedFooter
|
||||
}
|
||||
|
||||
case SET_BACKGROUND_COLOR:
|
||||
return {
|
||||
...state,
|
||||
backgroundColor: action.backgroundColor
|
||||
}
|
||||
|
||||
case SET_HEADER_BACKGROUND_COLOR:
|
||||
return {
|
||||
...state,
|
||||
headerBackgroundColor: action.headerBackgroundColor
|
||||
}
|
||||
|
||||
case SET_COLOR_SCHEME:
|
||||
return {
|
||||
...state,
|
||||
colorScheme: action.colorScheme
|
||||
}
|
||||
|
||||
case SET_BACKGROUND_IMAGE:
|
||||
return {
|
||||
...state,
|
||||
backgroundImage: action.backgroundImage
|
||||
}
|
||||
|
||||
case SET_BACKGROUND_IMAGE_OPACITY:
|
||||
return {
|
||||
...state,
|
||||
backgroundImageOpacity: action.backgroundImageOpacity
|
||||
}
|
||||
default:
|
||||
}
|
||||
return state
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import ReduxModule from '../abstract/reduxModule';
|
||||
import { toast } from 'react-toastify';
|
||||
import i18n from '../../../i18n';
|
||||
import { fromJS } from 'immutable';
|
||||
import { isLive } from '../../../common/constants';
|
||||
|
||||
const ADD_ALERT = 'Add alert';
|
||||
const REMOVE_ALERT = 'Remove alert';
|
||||
const REMOVE_ALL_ALERTS = 'Remove alert';
|
||||
|
||||
export class Alerts extends ReduxModule {
|
||||
getNamespace() {
|
||||
return '[Alert]';
|
||||
}
|
||||
|
||||
defineActions() {
|
||||
const addAlert = this.createAction(ADD_ALERT, (options) => options);
|
||||
const removeAlert = this.createAction(REMOVE_ALERT, (id) => id);
|
||||
const removeAllAlerts = this.createAction(REMOVE_ALL_ALERTS, () => {});
|
||||
|
||||
return {
|
||||
addAlert,
|
||||
removeAlert,
|
||||
removeAllAlerts
|
||||
};
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
return [];
|
||||
}
|
||||
|
||||
defineReducers() {
|
||||
return {
|
||||
[ADD_ALERT]: (state, { payload: options }) => {
|
||||
showAlert(options);
|
||||
|
||||
// handling breaking api errors (should be catch by backend)
|
||||
const newOptions = Array.isArray(options)
|
||||
? options.map((v) =>
|
||||
typeof v === 'string' && v.startsWith('Error: ') && isLive
|
||||
? i18n.t('common:alerts.error.somethingWrong2')
|
||||
: v
|
||||
)
|
||||
: options;
|
||||
|
||||
return state.concat(newOptions);
|
||||
},
|
||||
[REMOVE_ALERT]: (state, { payload: id }) => {
|
||||
return state.filter((alert) => alert.id !== id);
|
||||
},
|
||||
[REMOVE_ALL_ALERTS]: () => {
|
||||
return fromJS([]);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const alerts = new Alerts();
|
||||
alerts.init();
|
||||
|
||||
const backendErrs = ['Error: ', 'Can\'t exec search'];
|
||||
|
||||
const showAlert = (alertMessages) => {
|
||||
const alertsArr = Array.isArray(alertMessages)
|
||||
? alertMessages
|
||||
: [alertMessages];
|
||||
|
||||
alertsArr
|
||||
.map((alert) => {
|
||||
return typeof alert === 'string'
|
||||
? {
|
||||
message:
|
||||
isLive && backendErrs.some((v) => alert.startsWith(v))
|
||||
? i18n.t('common:alerts.error.somethingWrong2')
|
||||
: alert
|
||||
} // handling breaking api errors (should be catch by backend)
|
||||
: alert;
|
||||
})
|
||||
.map((alert) => {
|
||||
const interpolateParameters = alert ? alert.parameters : {};
|
||||
const i18nKey = alert && `alerts.${alert.type}.${alert.transKey}`;
|
||||
|
||||
if (alert) {
|
||||
toast(
|
||||
<Fragment>
|
||||
{alert.type ? (
|
||||
<p className="mb-2 text-uppercase">
|
||||
{i18n.t(`alerts.type.${oldValueMapping[alert.type]}`, {
|
||||
defaultValue: oldValueMapping[alert.type]
|
||||
})}
|
||||
</p>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
{(i18nKey &&
|
||||
i18n.t(i18nKey, {
|
||||
...interpolateParameters,
|
||||
defaultValue: alert.message || 'Unknown error'
|
||||
})) ||
|
||||
alert.message ||
|
||||
'Unknown error'}
|
||||
</Fragment>,
|
||||
{
|
||||
type: oldValueMapping[alert.type || 'warning']
|
||||
}
|
||||
);
|
||||
} else {
|
||||
toast.warn('Unknown error');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const oldValueMapping = {
|
||||
notice: 'success',
|
||||
warning: 'warning',
|
||||
error: 'error'
|
||||
};
|
||||
|
||||
export const addAlert = alerts.actions.addAlert;
|
||||
|
||||
export default alerts;
|
||||
@@ -0,0 +1,172 @@
|
||||
import * as api from '../../../api/loginApi';
|
||||
import $ from 'jquery';
|
||||
import reduxModule from '../abstract/reduxModule';
|
||||
import { tokenInject } from '../../utils/common';
|
||||
import { addAlert } from './alerts';
|
||||
import Cookies from 'cookies-js';
|
||||
import axios from 'axios';
|
||||
// import { TOGGLE_UPGRADE_PLAN } from './base';
|
||||
|
||||
const ACTIONS = {
|
||||
PENDING: 'Login pending',
|
||||
SAVE_USER_DATA: 'Save user data',
|
||||
SET_FORM_ERROR: 'Set form error',
|
||||
SET_RESTRICTIONS: 'Set user restrictions'
|
||||
};
|
||||
|
||||
export const AuthNS = '[Auth]';
|
||||
export const USER_LOGOUT = 'Logout user';
|
||||
const REFRESH_TOKEN = 'refreshToken';
|
||||
|
||||
class Auth extends reduxModule {
|
||||
getNamespace() {
|
||||
return AuthNS;
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
form: {
|
||||
error: ''
|
||||
},
|
||||
isAuthPending: true,
|
||||
token: '',
|
||||
refreshToken: '',
|
||||
user: {},
|
||||
userSubscription: '15d',
|
||||
userSubscriptionDate: '2017-03-01'
|
||||
};
|
||||
}
|
||||
|
||||
saveRefreshToken(refreshToken, rememberMe) {
|
||||
if (rememberMe) {
|
||||
localStorage.setItem(REFRESH_TOKEN, refreshToken);
|
||||
} else {
|
||||
Cookies.set(REFRESH_TOKEN, refreshToken);
|
||||
}
|
||||
}
|
||||
|
||||
clearRefreshToken() {
|
||||
localStorage.removeItem(REFRESH_TOKEN);
|
||||
delete axios.defaults.headers.common['Authorization'];
|
||||
Cookies.expire(REFRESH_TOKEN);
|
||||
}
|
||||
|
||||
getRefreshToken() {
|
||||
return Cookies.get(REFRESH_TOKEN) || localStorage.getItem(REFRESH_TOKEN);
|
||||
}
|
||||
|
||||
loginRequest(dispatch, promise, rememberMe) {
|
||||
dispatch(this.loginPending(true));
|
||||
return promise
|
||||
.then((data) => {
|
||||
const { token, refreshToken, user } = data;
|
||||
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; // to call api with axios
|
||||
this.saveRefreshToken(data.refreshToken, rememberMe);
|
||||
dispatch(this.saveUserData({ token, refreshToken, user }));
|
||||
dispatch(this.loginPending(false));
|
||||
dispatch(this.authSetError(''));
|
||||
// history.replace(location); //rerun auth guards for routes
|
||||
})
|
||||
.catch((error) => {
|
||||
dispatch(this.authSetError(error.msg));
|
||||
dispatch(this.loginPending(false));
|
||||
delete axios.defaults.headers.common['Authorization'];
|
||||
// history.replace(location); //rerun auth guards for routes
|
||||
});
|
||||
}
|
||||
|
||||
refreshLogin = () => {
|
||||
return (dispatch) => {
|
||||
const refreshToken = this.getRefreshToken();
|
||||
if (refreshToken) {
|
||||
this.loginRequest(dispatch, api.loginRefresh(refreshToken));
|
||||
} else {
|
||||
dispatch(this.loginPending(false));
|
||||
// history.replace(location); //rerun auth guards for routes
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
login = (email, password, rememberMe) => {
|
||||
return (dispatch) => {
|
||||
const validateEmail = /^(([^<>()[\]\\.,;:\s@]+(\.[^<>()[\]\\.,;:\s@]+)*)|(.+))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
const isEmailValid = validateEmail.test(email);
|
||||
if (isEmailValid) {
|
||||
this.loginRequest(dispatch, api.login({ email, password }), rememberMe);
|
||||
} else {
|
||||
dispatch(this.loginPending(false));
|
||||
dispatch(this.authSetError('Please enter valid email address'));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
logout = () => {
|
||||
return (dispatch) => {
|
||||
this.clearRefreshToken();
|
||||
// dispatch(this.saveUserData({ token: '' }));
|
||||
dispatch(this.userLogout(true));
|
||||
dispatch(this.loginPending(false));
|
||||
// history.push('/auth');
|
||||
};
|
||||
};
|
||||
|
||||
getRestrictions = () =>
|
||||
tokenInject((dispatch, getState, token) => {
|
||||
api
|
||||
.getRestrictions(token)
|
||||
.then((data) => {
|
||||
dispatch(this.setRestrictions(data));
|
||||
})
|
||||
.catch((errors) => {
|
||||
dispatch(addAlert(errors));
|
||||
});
|
||||
});
|
||||
|
||||
handleErrors = () => (dispatch) => {
|
||||
$(document).ajaxError((event, jqXHR, settings, thrownError) => {
|
||||
if (jqXHR.status === 402) {
|
||||
const response = jqXHR.responseJSON;
|
||||
const failedRestriction = response.failedRestriction;
|
||||
const restrictions = response.restrictions;
|
||||
const limit = restrictions.limits[failedRestriction];
|
||||
if (limit) {
|
||||
dispatch(this.setRestrictions(restrictions));
|
||||
// dispatch({ type: `[Base] ${TOGGLE_UPGRADE_PLAN}`, payload: true }); // uncomment when upgrade page is ready
|
||||
dispatch(
|
||||
addAlert({
|
||||
type: 'error',
|
||||
transKey: 'restriction',
|
||||
id: 'restriction'
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
defineActions() {
|
||||
this.loginPending = this.set(ACTIONS.PENDING, 'isAuthPending');
|
||||
this.saveUserData = this.merge(ACTIONS.SAVE_USER_DATA);
|
||||
this.authSetError = this.setIn(ACTIONS.SET_FORM_ERROR, ['form', 'error']);
|
||||
this.setRestrictions = this.mergeIn(ACTIONS.SET_RESTRICTIONS, [
|
||||
'user',
|
||||
'restrictions'
|
||||
]);
|
||||
this.userLogout = this.set(USER_LOGOUT, 'userLogout');
|
||||
|
||||
return {
|
||||
login: this.login,
|
||||
logout: this.logout,
|
||||
refreshLogin: this.refreshLogin,
|
||||
authSetError: this.authSetError,
|
||||
handleErrors: this.handleErrors,
|
||||
getRestrictions: this.getRestrictions
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const auth = new Auth();
|
||||
auth.init();
|
||||
|
||||
export const getRestrictions = auth.actions.getRestrictions;
|
||||
export default auth;
|
||||
@@ -0,0 +1,157 @@
|
||||
import reduxModule from '../abstract/reduxModule';
|
||||
import dashboards, { LOAD_DASHBOARDS } from '../appState/dashboards';
|
||||
import * as api from '../../../api/usersApi';
|
||||
|
||||
export const TOGGLE_UPGRADE_PLAN = 'TOGGLE_UPGRADE_PLAN';
|
||||
|
||||
export class Base extends reduxModule {
|
||||
getNamespace() {
|
||||
return '[Base]';
|
||||
}
|
||||
|
||||
changeUserPassword = (password, oldPassword, { token, dispatch }) => {
|
||||
dispatch(this.setSettingsPopupError(null));
|
||||
return api
|
||||
.changePassword(token, { password, oldPassword })
|
||||
.then(() => {
|
||||
dispatch(this.hideUserSettingsPopup());
|
||||
})
|
||||
.catch((response) => {
|
||||
dispatch(this.setSettingsPopupError(response[0].message));
|
||||
});
|
||||
};
|
||||
|
||||
defineActions() {
|
||||
// const toggleLangsDrop = this.toggle('TOGGLE_LANGS_DROP', 'isLangsDropVisible')
|
||||
// const hideLangsDrop = this.reset('HIDE_LANGS_DROP', 'isLangsDropVisible', false)
|
||||
// const toggleSidebar = this.toggle('TOGGLE_SIDEBAR', 'isSidebarCollapsed')
|
||||
const setDashboardTabs = this.createAction('SET_DASHBOARD_TABS');
|
||||
const chooseLanguage = this.createAction('CHOOSE_LANG');
|
||||
const showUserSettingsPopup = this.reset(
|
||||
'SHOW_USER_SETTINGS_DROP',
|
||||
'isSettingsPopupVisible',
|
||||
true
|
||||
);
|
||||
const hideUserSettingsPopup = (this.hideUserSettingsPopup = this.reset(
|
||||
'HIDE_USER_SETTINGS_DROP',
|
||||
'isSettingsPopupVisible',
|
||||
false
|
||||
));
|
||||
const changeUserPassword = this.thunkAction(
|
||||
'CHANGE_USER_PASSWORD',
|
||||
this.changeUserPassword
|
||||
);
|
||||
const setSettingsPopupError = (this.setSettingsPopupError = this.set(
|
||||
'SET_SETTINGS_POPUP_ERROR',
|
||||
'settingsPopupError'
|
||||
));
|
||||
const toggleResponsiveMenu = this.toggle(
|
||||
'TOGGLE_RESPONSIVE_MENU',
|
||||
'responsiveMenuVisible'
|
||||
);
|
||||
const toggleUpgradeModal = this.toggle(
|
||||
TOGGLE_UPGRADE_PLAN,
|
||||
'isUpgradeVisible'
|
||||
);
|
||||
const toggleWebTour = this.toggle('TOGGLE_WEBTOUR', 'isTourOpen');
|
||||
|
||||
return {
|
||||
// toggleLangsDrop,
|
||||
chooseLanguage,
|
||||
// hideLangsDrop,
|
||||
// toggleSidebar,
|
||||
setDashboardTabs,
|
||||
showUserSettingsPopup,
|
||||
hideUserSettingsPopup,
|
||||
changeUserPassword,
|
||||
setSettingsPopupError,
|
||||
toggleResponsiveMenu,
|
||||
toggleUpgradeModal,
|
||||
toggleWebTour
|
||||
};
|
||||
}
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
tabs: {
|
||||
/*'dashboard': {
|
||||
items: []
|
||||
},*/
|
||||
search: {
|
||||
items: [
|
||||
{ title: 'search', url: 'search' },
|
||||
{ title: 'sourceIndex', url: 'source-index' },
|
||||
{ title: 'sourceLists', url: 'source-lists' }
|
||||
],
|
||||
icon: 'pe-7s-search'
|
||||
},
|
||||
analyze: {
|
||||
items: [
|
||||
// {title: 'welcome', url: 'welcome'},
|
||||
{ title: 'savedAnalysis', url: 'saved' },
|
||||
{ title: 'createAnalysis', url: 'create' }
|
||||
],
|
||||
icon: 'pe-7s-graph1'
|
||||
},
|
||||
share: {
|
||||
items: [
|
||||
{ title: 'notifications', url: 'notifications' },
|
||||
{ title: 'manageEmails', url: 'manage-emails', masterOnly: true },
|
||||
{
|
||||
title: 'manageRecipients',
|
||||
url: 'manage-recipients',
|
||||
masterOnly: true
|
||||
},
|
||||
{ title: 'export', url: 'export' }
|
||||
],
|
||||
icon: 'pe-7s-share'
|
||||
}
|
||||
},
|
||||
// isSidebarCollapsed: false,
|
||||
isUserSettingsDropVisible: false,
|
||||
// isLangsDropVisible: false,
|
||||
isSettingsPopupVisible: false,
|
||||
settingsPopupError: '',
|
||||
isThereSomethingNew: true,
|
||||
langs: ['en', 'ar', 'fr'],
|
||||
// langs: ['en', 'ar', 'fr', 'es', 'de', , 'he', 'nl', 'pt'],
|
||||
activeLang: '',
|
||||
rtlLang: false,
|
||||
responsiveMenuVisible: false,
|
||||
isUpgradeVisible: false
|
||||
};
|
||||
}
|
||||
|
||||
defineReducers() {
|
||||
this.addExternalReducer(
|
||||
dashboards.ns(`${LOAD_DASHBOARDS} fulfilled`),
|
||||
(state, { payload: dashboards }) => {
|
||||
const dashboardTabs = dashboards.map((d) => ({
|
||||
url: d.id,
|
||||
title: d.name
|
||||
}));
|
||||
return state.mergeIn(['tabs', 'dashboard', 'items'], dashboardTabs);
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
CHOOSE_LANG: (state, { payload: lang }) => {
|
||||
const langsAvailable = state.get('langs');
|
||||
const language = langsAvailable.includes(lang) ? lang : 'en';
|
||||
const rtlLanguages = ['ar', 'he'];
|
||||
const rtlLang = rtlLanguages.includes(language);
|
||||
const dir = rtlLang ? 'rtl' : 'ltr';
|
||||
document.documentElement.dir = dir; // set page direction
|
||||
document.documentElement.lang = language;
|
||||
return state.merge({
|
||||
activeLang: language,
|
||||
rtlLang: rtlLang
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new Base();
|
||||
instance.init();
|
||||
export default instance;
|
||||
@@ -0,0 +1,168 @@
|
||||
import {createAction, handleActions} from 'redux-actions'
|
||||
import {fromJS} from 'immutable'
|
||||
import {thunkAction} from '../../utils/common'
|
||||
import * as api from '../../../api/registrationApi'
|
||||
import {push} from 'react-router-redux'
|
||||
import { addAlert } from './alerts'
|
||||
import i18n from '../../../i18n'
|
||||
const NS = '[RESET PASS]'
|
||||
/* const GET_BILLING_PLANS = `${NS} Get billing plans`
|
||||
const SELECT_BILLING_PLAN = `${NS} Select billing plan`
|
||||
const SEND_REGISTER_REQUEST = `${NS} Send register request`
|
||||
const FINISH_REGISTER = `${NS} Finish register`
|
||||
const SHOW_REGISTER_ERROR = `${NS} Show register error` */
|
||||
const REQUEST_PASSWORD_RESET = `${NS} Request password reset`
|
||||
const CONFIRM_PASSWORD_RESET = `${NS} Confirm password reset`
|
||||
const CLEAR_MESSAGES = `${NS} Clear messages`
|
||||
/*
|
||||
const getBillingPlans = thunkAction(GET_BILLING_PLANS, ({token, fulfilled}) => {
|
||||
return api
|
||||
.getBillingPlans(token)
|
||||
.then((plans) => {
|
||||
fulfilled(plans)
|
||||
})
|
||||
}, true)
|
||||
|
||||
const selectBillingPlan = createAction(SELECT_BILLING_PLAN, (billingPlan) => ({billingPlan}))
|
||||
|
||||
const showRegisterError = createAction(SHOW_REGISTER_ERROR)
|
||||
|
||||
const sendRegisterRequest = thunkAction(SEND_REGISTER_REQUEST, (formValues, {token, fulfilled, getState, dispatch}) => {
|
||||
|
||||
const billingPlan = getState().getIn(['common', 'register', 'selectedBillingPlan'])
|
||||
const privatePerson = Boolean(formValues.privatePerson)
|
||||
|
||||
let payload = {}
|
||||
Object.assign(payload, formValues, {
|
||||
billingPlanId: billingPlan.id,
|
||||
privatePerson: privatePerson
|
||||
})
|
||||
|
||||
if (privatePerson) {
|
||||
delete payload.organizationName
|
||||
delete payload.organizationAddress
|
||||
delete payload.organizationEmail
|
||||
delete payload.organizationPhone
|
||||
}
|
||||
|
||||
return api
|
||||
.sendRegistrationRequest(token, payload)
|
||||
.then((response) => {
|
||||
fulfilled(response)
|
||||
dispatch(showRegisterError(null))
|
||||
dispatch(push('/auth/register-finish'))
|
||||
})
|
||||
.catch((response) => {
|
||||
dispatch(showRegisterError(response[0]))
|
||||
throw response
|
||||
})
|
||||
|
||||
}, true)
|
||||
|
||||
const finishRegistration = thunkAction(FINISH_REGISTER, (formValues, {getState, token, fulfilled}) => {
|
||||
let verificationCode = getState().getIn(['common', 'register', 'registrationCode'])
|
||||
const payload = Object.assign({}, {
|
||||
code: verificationCode,
|
||||
card: {
|
||||
creditCardNumber: formValues.creditCardNumber,
|
||||
CVV: formValues.CVV,
|
||||
expireMonth: formValues.expireMonth,
|
||||
expireYear: formValues.expireYear,
|
||||
address: {
|
||||
country: formValues.country,
|
||||
city: formValues.city,
|
||||
street: formValues.street,
|
||||
postalCode: formValues.postalCode
|
||||
}
|
||||
}
|
||||
})
|
||||
return api
|
||||
.finishRegistration(token, payload)
|
||||
.then((response) => {
|
||||
fulfilled(response)
|
||||
})
|
||||
}, true) */
|
||||
|
||||
const requestPasswordReset = thunkAction(REQUEST_PASSWORD_RESET, (email, {fulfilled}) => {
|
||||
return api
|
||||
.requestPasswordReset(null, {email})
|
||||
.then(() => {
|
||||
fulfilled(
|
||||
i18n.t('loginApp:messages.forgotPasswordSubmit', { email: email })
|
||||
);
|
||||
})
|
||||
})
|
||||
|
||||
const confirmPasswordReset = thunkAction(REQUEST_PASSWORD_RESET, (confirmationToken, password, {dispatch}) => {
|
||||
return api
|
||||
.confirmPasswordReset(null, {confirmationToken, password})
|
||||
.then(() => {
|
||||
dispatch(push('/auth/login'))
|
||||
dispatch(addAlert({type: 'notice', message: i18n.t('loginApp:messages.passwordUpdated')}))
|
||||
})
|
||||
})
|
||||
|
||||
const clearMessages = createAction(CLEAR_MESSAGES)
|
||||
|
||||
export const actions = {
|
||||
/* getBillingPlans,
|
||||
selectBillingPlan,
|
||||
sendRegisterRequest,
|
||||
finishRegistration, */
|
||||
requestPasswordReset,
|
||||
confirmPasswordReset,
|
||||
// showRegisterError,
|
||||
clearMessages
|
||||
}
|
||||
|
||||
export const initialState = fromJS({
|
||||
selectedBillingPlan: '',
|
||||
billingPlans: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
registrationCode: null,
|
||||
successMessage: null
|
||||
})
|
||||
/*
|
||||
const toggleLoading = (state, {payload: {isPending}}) => {
|
||||
return state.set('isLoading', isPending)
|
||||
} */
|
||||
|
||||
export default handleActions({
|
||||
/*
|
||||
[`${GET_BILLING_PLANS} fulfilled`]: (state, {payload: plans}) => state.set('billingPlans', plans),
|
||||
|
||||
[SELECT_BILLING_PLAN]: (state, {payload: {billingPlan}}) => {
|
||||
console.log(billingPlan)
|
||||
return state.set('selectedBillingPlan', billingPlan)
|
||||
},
|
||||
|
||||
[`${SEND_REGISTER_REQUEST} pending`]: toggleLoading,
|
||||
[`${SEND_REGISTER_REQUEST} fulfilled`]: (state, {payload: response}) => {
|
||||
return state.merge({
|
||||
'registrationCode': response.code,
|
||||
'successMessage': response.message
|
||||
})
|
||||
},
|
||||
|
||||
[`${FINISH_REGISTER} pending`]: toggleLoading,
|
||||
[`${FINISH_REGISTER} fulfilled`]: (state, {payload: response}) => {
|
||||
return state.set('successMessage', response.message)
|
||||
}, */
|
||||
[`${REQUEST_PASSWORD_RESET} fulfilled`]: (state, {payload: message}) => {
|
||||
return state.set('successMessage', message)
|
||||
},
|
||||
[`${CONFIRM_PASSWORD_RESET} fulfilled`]: (state, {payload: message}) => {
|
||||
return state.set('successMessage', message)
|
||||
},
|
||||
[CLEAR_MESSAGES]: (state) => {
|
||||
return state.set('successMessage', null)
|
||||
}
|
||||
/*
|
||||
[SHOW_REGISTER_ERROR]: (state, {payload: error}) => {
|
||||
return state.set('error', error)
|
||||
},
|
||||
|
||||
[`${GET_BILLING_PLANS} pending`]: toggleLoading */
|
||||
|
||||
}, initialState)
|
||||
@@ -0,0 +1,13 @@
|
||||
import { handleActions } from 'redux-actions'
|
||||
import { fromJS } from 'immutable'
|
||||
import { LOCATION_CHANGE } from 'react-router-redux'
|
||||
|
||||
export const initialState = fromJS({
|
||||
locationBeforeTransitions: null
|
||||
})
|
||||
|
||||
export default handleActions({
|
||||
[LOCATION_CHANGE]: (state, {payload}) => {
|
||||
return state.set('locationBeforeTransitions', payload)
|
||||
}
|
||||
}, initialState)
|
||||
@@ -0,0 +1,160 @@
|
||||
import { combineReducers } from 'redux-immutable';
|
||||
|
||||
// App state and actions
|
||||
import register, {
|
||||
actions as registerActions
|
||||
} from './modules/common/register';
|
||||
import routing from './modules/routing/routing';
|
||||
|
||||
import sidebar, { actions as sidebarActions } from './modules/appState/sidebar';
|
||||
import search, { actions as searchActions } from './modules/appState/search';
|
||||
import searchByFilters, {
|
||||
actions as searchByFiltersActions
|
||||
} from './modules/appState/searchByFilters';
|
||||
import sourcesState, {
|
||||
actions as sourcesStateActions
|
||||
} from './modules/appState/sourcesState';
|
||||
import articles, {
|
||||
actions as articleActions
|
||||
} from './modules/appState/articles';
|
||||
import shareTabs, {
|
||||
actions as shareTabsActions,
|
||||
NOTIFICATION_TABLES,
|
||||
RECEIVER_TABLES,
|
||||
RECIPIENT_FORM_TABLES,
|
||||
GROUP_FORM_TABLES,
|
||||
NOTIFICATION_SUBSCREENS,
|
||||
RECEIVER_SUBSCREENS
|
||||
} from './modules/appState/share/tabs';
|
||||
import { actions as shareFormsCommonActions } from './modules/appState/share/shareForms';
|
||||
import exportFeeds, {
|
||||
actions as exportFeedsActions
|
||||
} from './modules/appState/share/exportFeeds';
|
||||
import ThemeOptions, { themeActions } from './modules/appState/themeOptions';
|
||||
|
||||
//inherited from reduxModule
|
||||
import auth, { AuthNS, USER_LOGOUT } from './modules/common/auth';
|
||||
import base from './modules/common/base';
|
||||
import alerts from './modules/common/alerts';
|
||||
|
||||
import dashboards from './modules/appState/dashboards';
|
||||
|
||||
import myEmailsTable from './modules/appState/share/tables/myEmailsTable';
|
||||
import publishedEmailsTable from './modules/appState/share/tables/publishedEmailsTable';
|
||||
import recipientsTable from './modules/appState/share/tables/recipientsTable';
|
||||
import groupsTable from './modules/appState/share/tables/groupsTable';
|
||||
import emailHistoryTable from './modules/appState/share/tables/receiverForm/emailHistoryTable';
|
||||
import receiverSubscriptionsTable from './modules/appState/share/tables/receiverForm/receiverSubscriptionsTable';
|
||||
import receiverGroupsTable from './modules/appState/share/tables/receiverForm/receiverGroupsTable';
|
||||
import receiverRecipientsTable from './modules/appState/share/tables/receiverForm/receiverRecipientsTable';
|
||||
import emailsTable from './modules/appState/share/tables/emailsTable';
|
||||
import emailFiltersTable from './modules/appState/share/tables/emailFiltersTable';
|
||||
|
||||
import alertForm from './modules/appState/share/forms/alertForm';
|
||||
import newsletterForm from './modules/appState/share/forms/newsletterForm';
|
||||
import recipientForm from './modules/appState/share/forms/recipientForm';
|
||||
import groupForm from './modules/appState/share/forms/groupForm';
|
||||
|
||||
import themes from './modules/appState/share/emailThemes/themes';
|
||||
import analyze, { analyzeActions } from './modules/appState/analyze/analyze';
|
||||
|
||||
const shareTables = combineReducers({
|
||||
[NOTIFICATION_TABLES.MY_EMAILS]: myEmailsTable.reducers,
|
||||
[NOTIFICATION_TABLES.PUBLISHED]: publishedEmailsTable.reducers,
|
||||
[RECEIVER_TABLES.RECIPIENTS]: recipientsTable.reducers,
|
||||
[RECEIVER_TABLES.GROUPS]: groupsTable.reducers,
|
||||
emails: emailsTable.reducers,
|
||||
emailFilters: emailFiltersTable.reducers,
|
||||
receiverForm: combineReducers({
|
||||
[RECIPIENT_FORM_TABLES.GROUPS]: receiverGroupsTable.reducers,
|
||||
[RECIPIENT_FORM_TABLES.SUBSCRIPTIONS]: receiverSubscriptionsTable.reducers,
|
||||
[RECIPIENT_FORM_TABLES.EMAIL_HISTORY]: emailHistoryTable.reducers,
|
||||
[GROUP_FORM_TABLES.RECIPIENTS]: receiverRecipientsTable.reducers
|
||||
})
|
||||
});
|
||||
|
||||
const shareForms = combineReducers({
|
||||
[NOTIFICATION_SUBSCREENS.ALERT_FORM]: alertForm.reducers,
|
||||
[NOTIFICATION_SUBSCREENS.NEWSLETTER_FORM]: newsletterForm.reducers,
|
||||
[RECEIVER_SUBSCREENS.RECIPIENT_FORM]: recipientForm.reducers,
|
||||
[RECEIVER_SUBSCREENS.GROUP_FORM]: groupForm.reducers
|
||||
});
|
||||
|
||||
const appReducers = combineReducers({
|
||||
routing,
|
||||
common: combineReducers({
|
||||
base: base.reducers,
|
||||
auth: auth.reducers,
|
||||
alerts: alerts.reducers,
|
||||
register
|
||||
}),
|
||||
appState: combineReducers({
|
||||
sidebar,
|
||||
search,
|
||||
searchByFilters,
|
||||
sourcesState,
|
||||
analyze,
|
||||
articles,
|
||||
dashboards: dashboards.reducers,
|
||||
themeOptions: ThemeOptions,
|
||||
share: combineReducers({
|
||||
tabs: shareTabs,
|
||||
forms: shareForms,
|
||||
tables: shareTables,
|
||||
themes: themes.reducers,
|
||||
exportFeeds
|
||||
})
|
||||
})
|
||||
});
|
||||
|
||||
export function rootReducers(state, action) {
|
||||
if (action.type === `${AuthNS} ${USER_LOGOUT}`) {
|
||||
state = undefined; // to clear state when logout
|
||||
}
|
||||
|
||||
return appReducers(state, action);
|
||||
}
|
||||
|
||||
export const shareFormsActions = {
|
||||
alert: alertForm.actions,
|
||||
newsletter: alertForm.actions,
|
||||
recipient: recipientForm.actions,
|
||||
group: groupForm.actions
|
||||
};
|
||||
|
||||
export const shareTablesActions = {
|
||||
[NOTIFICATION_TABLES.MY_EMAILS]: myEmailsTable.actions,
|
||||
[NOTIFICATION_TABLES.PUBLISHED]: publishedEmailsTable.actions,
|
||||
[RECEIVER_TABLES.RECIPIENTS]: recipientsTable.actions,
|
||||
[RECEIVER_TABLES.GROUPS]: groupsTable.actions,
|
||||
emails: emailsTable.actions,
|
||||
emailFilters: emailFiltersTable.actions,
|
||||
receiverForm: {
|
||||
[RECIPIENT_FORM_TABLES.GROUPS]: receiverGroupsTable.actions,
|
||||
[RECIPIENT_FORM_TABLES.SUBSCRIPTIONS]: receiverSubscriptionsTable.actions,
|
||||
[RECIPIENT_FORM_TABLES.EMAIL_HISTORY]: emailHistoryTable.actions,
|
||||
[GROUP_FORM_TABLES.RECIPIENTS]: receiverRecipientsTable.actions
|
||||
}
|
||||
};
|
||||
|
||||
export const rootActions = Object.assign(
|
||||
{},
|
||||
auth.actions,
|
||||
alerts.actions,
|
||||
base.actions,
|
||||
registerActions,
|
||||
sidebarActions,
|
||||
searchActions,
|
||||
analyzeActions,
|
||||
searchByFiltersActions,
|
||||
sourcesStateActions,
|
||||
shareTabsActions,
|
||||
shareFormsCommonActions,
|
||||
themes.actions,
|
||||
themeActions,
|
||||
articleActions,
|
||||
exportFeedsActions,
|
||||
dashboards.actions,
|
||||
{ shareTables: shareTablesActions },
|
||||
{ shareForms: shareFormsActions }
|
||||
);
|
||||
@@ -0,0 +1,13 @@
|
||||
import React from 'react'
|
||||
import { createDevTools } from 'redux-devtools'
|
||||
import LogMonitor from 'redux-devtools-log-monitor'
|
||||
import DockMonitor from 'redux-devtools-dock-monitor'
|
||||
|
||||
export default createDevTools(
|
||||
<DockMonitor
|
||||
defaultIsVisible={false}
|
||||
toggleVisibilityKey='ctrl-h'
|
||||
changePositionKey='ctrl-q' >
|
||||
<LogMonitor preserveScrollTop={false} />
|
||||
</DockMonitor>
|
||||
)
|
||||
@@ -0,0 +1,46 @@
|
||||
import {createAction} from 'redux-actions'
|
||||
import {addAlert} from '../modules/common/alerts'
|
||||
|
||||
export const tokenInject = (fn) =>
|
||||
(dispatch, getState) =>
|
||||
fn(dispatch, getState, getState().getIn(['common', 'auth', 'token']))
|
||||
|
||||
export const thunkAction = (actionName, actionMethod, emitPending = false, customPendingAction = false) => {
|
||||
const fulfilledAction = createAction(`${actionName} fulfilled`)
|
||||
const pendingAction = customPendingAction ||
|
||||
createAction(`${actionName} pending`, (isPending, success) => ({isPending, success}))
|
||||
|
||||
return (...args) => {
|
||||
return tokenInject((dispatch, getState, token) => {
|
||||
|
||||
const fulfilled = (...fArgs) => {
|
||||
dispatch(fulfilledAction(...fArgs))
|
||||
emitPending && dispatch(pendingAction(false, true))
|
||||
}
|
||||
|
||||
const onError = (errors) => {
|
||||
dispatch(addAlert(errors))
|
||||
emitPending && dispatch(pendingAction(false, false))
|
||||
}
|
||||
|
||||
emitPending && dispatch(pendingAction(true))
|
||||
|
||||
let result
|
||||
try {
|
||||
result = actionMethod(...args, {dispatch, getState, token, fulfilled})
|
||||
} catch (e) {
|
||||
console.error('Error in thunkAction()')
|
||||
console.error(e)
|
||||
throw e
|
||||
}
|
||||
if (result instanceof Promise) {
|
||||
result.catch(onError)
|
||||
}
|
||||
return result
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export const routerSelectLocationState = (state) => {
|
||||
return state.get('routing').toJS()
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { connect } from 'react-redux'
|
||||
import { rootActions } from '../root'
|
||||
|
||||
//This is recursive version of standard redux bindActionCreators
|
||||
function bindActionCreatorsRecursive (actions, dispatch) {
|
||||
if (typeof dispatch !== 'function') {
|
||||
throw new TypeError('Action wrapper needs a dispatch function')
|
||||
}
|
||||
return Object.keys(actions).reduce(function (acc, key) {
|
||||
if (typeof actions[key] === 'function') {
|
||||
acc[key] = function () {
|
||||
return dispatch(actions[key].apply(null, arguments))
|
||||
}
|
||||
} else if (actions[key] !== null && typeof actions[key] === 'object') {
|
||||
acc[key] = bindActionCreatorsRecursive(actions[key], dispatch)
|
||||
}
|
||||
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
export default function reduxConnect (storePropName = 'store', storePath) {
|
||||
return (component) => {
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
[storePropName]: storePath ? state.getIn(storePath).toJS() : state.toJS()
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
actions: bindActionCreatorsRecursive(rootActions, dispatch)
|
||||
})
|
||||
|
||||
return connect(mapStateToProps, mapDispatchToProps)(component)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export function reduxActions () {
|
||||
return (component) => {
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
actions: bindActionCreatorsRecursive(rootActions, dispatch)
|
||||
})
|
||||
|
||||
return connect(null, mapDispatchToProps)(component)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
export const ADV_FILTERS_LIMIT = 7 //for advanced filters client-side paging
|
||||
|
||||
export const filtersFromServerFormat = function (advancedFilters) {
|
||||
const allFilters = {}
|
||||
const pages = {}
|
||||
for (let groupName in advancedFilters) {
|
||||
let filters = advancedFilters[groupName].data
|
||||
allFilters[groupName] = filters
|
||||
pages[groupName] = {count: ADV_FILTERS_LIMIT, totalCount: filters.length}
|
||||
}
|
||||
return {pages, allFilters}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import {fromJS} from 'immutable'
|
||||
import {ADV_FILTERS_LIMIT} from '../../modules/appState/search'
|
||||
/**
|
||||
* Commenting helpers
|
||||
*/
|
||||
|
||||
export const indexById = (arr, id) => arr.findIndex((a) => a.id === id)
|
||||
|
||||
const changeArticleComments = (fn) => {
|
||||
return (articles, articleId, comment) => {
|
||||
let result = articles.toJS()
|
||||
const articleIndex = indexById(result, articleId)
|
||||
if (articleIndex !== -1) {
|
||||
fn(result[articleIndex].comments, comment)
|
||||
} else {
|
||||
console.error(`search.js - cannot find article with id ${articleId}`)
|
||||
}
|
||||
return fromJS(result)
|
||||
}
|
||||
}
|
||||
|
||||
export const loadMoreComments = changeArticleComments((comments, newComments) => {
|
||||
comments.data = comments.data.concat(newComments)
|
||||
comments.count += newComments.length
|
||||
})
|
||||
|
||||
export const addComment = changeArticleComments((comments, comment) => {
|
||||
comments.data.unshift(comment)
|
||||
comments.count++
|
||||
comments.totalCount++
|
||||
})
|
||||
|
||||
export const updateComment = changeArticleComments((comments, comment) => {
|
||||
const commentIndex = indexById(comments.data, comment.id)
|
||||
if (commentIndex !== -1) {
|
||||
comments.data[commentIndex] = comment
|
||||
} else {
|
||||
console.error(`search.js::updateComment() cannot find comment with id ${comment.id}`)
|
||||
}
|
||||
})
|
||||
|
||||
export const deleteComment = changeArticleComments((comments, commentId) => {
|
||||
const commentIndex = indexById(comments.data, commentId)
|
||||
if (commentIndex !== -1) {
|
||||
comments.data.splice(commentIndex, 1)
|
||||
comments.count--
|
||||
comments.totalCount--
|
||||
} else {
|
||||
console.error(`search.js::deleteComment() cannot find comment with id ${commentId}`)
|
||||
}
|
||||
})
|
||||
|
||||
//End commenting helpers
|
||||
|
||||
//Ensure that "selectedFilters" is always in "allFilters"
|
||||
//allFilters = {groupName: [{value: "", count: ""}] }
|
||||
//selectedFilters = {groupName: {"value": "count"}}
|
||||
export const mergeAdvancedFilters = (allFilters, selectedFilters, pages) => {
|
||||
|
||||
const _insertFilter = (groupName, value, count) => {
|
||||
let group = allFilters[groupName] || (allFilters[groupName] = [])
|
||||
|
||||
const found = group.find((item) => item.value === value)
|
||||
if (!found) {
|
||||
group.unshift({value, count})
|
||||
let pageGroup = pages[groupName] || (pages[groupName] = {count: ADV_FILTERS_LIMIT, totalCount: 0})
|
||||
pageGroup.totalCount++
|
||||
}
|
||||
}
|
||||
|
||||
for (let groupName in selectedFilters) {
|
||||
if (selectedFilters.hasOwnProperty(groupName)) {
|
||||
let group = selectedFilters[groupName]
|
||||
|
||||
for (let value in group) {
|
||||
if (group.hasOwnProperty(value)) {
|
||||
_insertFilter(groupName, value, group[value])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
import {fromJS} from 'immutable'
|
||||
|
||||
const categoriesFromState = (fn) => {
|
||||
return (state, ...args) => {
|
||||
const categories = state.getIn(['appState', 'sidebar', 'categories']).toJS()
|
||||
return fromJS(fn(categories, ...args))
|
||||
}
|
||||
}
|
||||
|
||||
const walkCategories = (fn) => {
|
||||
return (categories, ...args) => {
|
||||
const walker = (array) => {
|
||||
return array.map((category) => {
|
||||
category.childes = walker(category.childes)
|
||||
fn(category, ...args)
|
||||
return category
|
||||
})
|
||||
}
|
||||
return walker(categories)
|
||||
}
|
||||
}
|
||||
|
||||
const feedHelper = (fn) => categoriesFromState(walkCategories(fn))
|
||||
|
||||
//return categories without deleted feed
|
||||
export const deleteFeed = feedHelper((category, feedCategoryId, feedId) => {
|
||||
if (category.id === feedCategoryId) {
|
||||
category.feeds = category.feeds.filter((feed) => feed.id !== feedId)
|
||||
}
|
||||
})
|
||||
|
||||
export const addFeed = feedHelper((category, feedCategoryId, feed) => {
|
||||
if (category.id === feedCategoryId) {
|
||||
category.feeds.push(feed)
|
||||
}
|
||||
})
|
||||
|
||||
export const renameFeed = feedHelper((category, feedId, newFeedName, feedCategoryId) => {
|
||||
if (category.id === feedCategoryId) {
|
||||
category.feeds = category.feeds.map((feed) => {
|
||||
if (feed.id === feedId) {
|
||||
feed.name = newFeedName
|
||||
}
|
||||
return feed
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const _deleteCategory = (categories, categoryId) => {
|
||||
return categories.map((category) => {
|
||||
if (category.childes.length > 0) {
|
||||
const initChildesLength = category.childes.length
|
||||
category.childes = category.childes.filter((childCategory) => {
|
||||
return childCategory.id !== categoryId
|
||||
})
|
||||
const childesLengthAfterFilter = category.childes.length
|
||||
// if there wasn't deletion continue to search
|
||||
if (childesLengthAfterFilter === initChildesLength) {
|
||||
category.childes = _deleteCategory(category.childes, categoryId)
|
||||
}
|
||||
}
|
||||
return category
|
||||
})
|
||||
}
|
||||
|
||||
//return categories without deleted category
|
||||
export const deleteCategory = categoriesFromState(_deleteCategory)
|
||||
|
||||
export const addCategory = feedHelper((category, parentId, newCategory) => {
|
||||
if (category.id === parentId) {
|
||||
category.childes.push(newCategory)
|
||||
}
|
||||
})
|
||||
|
||||
export const renameCategory = feedHelper((category, categoryId, newCategoryName) => {
|
||||
if (category.id === categoryId) {
|
||||
category.name = newCategoryName
|
||||
}
|
||||
})
|
||||
|
||||
export const checkIfDraggedCategoryDragToItsChild = (categories, newCategoryId) => {
|
||||
return categories.find((category) => {
|
||||
if (category.childes.length > 0) {
|
||||
checkIfDraggedCategoryDragToItsChild(category.childes, newCategoryId)
|
||||
}
|
||||
|
||||
return category.id === newCategoryId
|
||||
})
|
||||
}
|
||||
|
||||
export const findFeedById = (categories, feedId) => {
|
||||
let feed = null
|
||||
categories.forEach((category) => {
|
||||
if (!feed && category.feeds.length > 0) {
|
||||
const findResult = category.feeds.filter(feed => feed.id === feedId)
|
||||
feed = findResult[0] || null
|
||||
if (feed) {
|
||||
feed.category = category.id
|
||||
}
|
||||
|
||||
if (!feed && category.childes.length > 0) {
|
||||
feed = findFeedById(category.childes, feedId)
|
||||
}
|
||||
}
|
||||
})
|
||||
return feed
|
||||
}
|
||||
Reference in New Issue
Block a user