at the end of the day, it was inevitable

This commit is contained in:
Mo Elzubeir
2022-12-09 08:36:26 -06:00
commit 1218570914
1768 changed files with 887087 additions and 0 deletions
@@ -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