diff --git a/src/api/call-blocking.js b/src/api/call-blocking.js index 72972d75..4071d808 100644 --- a/src/api/call-blocking.js +++ b/src/api/call-blocking.js @@ -1,5 +1,6 @@ import _ from 'lodash'; +import Vue from 'vue'; import { enableBlockIn, @@ -147,3 +148,76 @@ export function getPrivacyCallBlocking(id) { }); }); } + +export function removeNumberFromList(id, field, value) { + return new Promise((resolve, reject)=>{ + Promise.resolve().then(()=>{ + return getPreferences(id); + }).then((result)=>{ + var prefs = _.cloneDeep(result); + delete prefs._links; + prefs[field] = _.get(prefs, field, []).filter((number) => { + return number !== value; + }); + return Vue.http.put('api/subscriberpreferences/' + id, prefs); + }).then(()=>{ + resolve(); + }).catch((err)=>{ + reject(err); + }); + }); +} + +export function removeFromIncomingListByNumber(id, number) { + return new Promise((resolve, reject) => { + removeNumberFromList(id, 'block_in_list', number).then(() => { + resolve() + }).catch((err) => { + reject(err); + }); + }); +} + +export function removeFromOutgoingListByNumber(id, number) { + return new Promise((resolve, reject) => { + removeNumberFromList(id, 'block_out_list', number).then(() => { + resolve() + }).catch((err) => { + reject(err); + }); + }); +} + +export function toggleNumberInBothLists(options) { + return new Promise((resolve, reject) => { + Promise.resolve().then(() => { + return getPreferences(options.id); + }).then((result) => { + let prefs = _.cloneDeep(result); + delete prefs._links; + prefs['block_in_list'] = _.get(prefs, 'block_in_list', []); + prefs['block_out_list'] = _.get(prefs, 'block_out_list', []); + if (options.block_in_list === 'add') { + prefs['block_in_list'] = [options.number].concat(prefs['block_in_list']); + } + else if (options.block_in_list === 'remove') { + prefs['block_in_list'] = prefs['block_in_list'].filter((number) => { + return number !== options.number; + }); + } + if (options.block_out_list === 'add') { + prefs['block_out_list'] = [options.number].concat(prefs['block_out_list']); + } + else if (options.block_out_list === 'remove') { + prefs['block_out_list'] = prefs['block_out_list'].filter((number) => { + return number !== options.number; + }); + } + return Vue.http.put('api/subscriberpreferences/' + options.id, prefs); + }).then(() => { + resolve(); + }).catch((err) => { + reject(err); + }); + }); +} diff --git a/src/api/conversations.js b/src/api/conversations.js index 9a544d50..8b0af2d5 100644 --- a/src/api/conversations.js +++ b/src/api/conversations.js @@ -2,10 +2,14 @@ import _ from 'lodash' import { saveAs } from 'file-saver' import Vue from 'vue' +import { + getIncomingCallBlocking, + getOutgoingCallBlocking +} from './call-blocking' import { getList } from './common' export function getConversations(options) { - return new Promise((resolve, reject)=>{ + return new Promise((resolve, reject) => { let type = _.get(options, 'type', null); let params ={ subscriber_id: _.get(options, 'subscriberId'), @@ -32,7 +36,7 @@ export function getConversations(options) { } export function downloadVoiceMail(id) { - return new Promise((resolve, reject)=>{ + return new Promise((resolve, reject) => { Vue.http.get('api/voicemailrecordings/' + id, { responseType: 'blob' }) .then((res) => { return res.blob(); @@ -46,7 +50,7 @@ export function downloadVoiceMail(id) { } export function downloadFax(id) { - return new Promise((resolve, reject)=>{ + return new Promise((resolve, reject) => { Vue.http.get('api/faxrecordings/' + id, { responseType: 'blob' }) .then((res) => { return res.blob(); @@ -60,7 +64,7 @@ export function downloadFax(id) { } export function playVoiceMail(options) { - return new Promise((resolve, reject)=>{ + return new Promise((resolve, reject) => { let params = { format: options.format }; Vue.http.get(`api/voicemailrecordings/${options.id}`, { params: params, responseType: 'blob' }) .then((res) => { @@ -70,3 +74,24 @@ export function playVoiceMail(options) { }); }); } + +export function getIncomingBlocked(id) { + return new Promise((resolve, reject) => { + getIncomingCallBlocking(id).then((list) => { + resolve(list) + }).catch((err) => { + reject(err); + }); + }); +} + +export function getOutgoingBlocked(id) { + return new Promise((resolve, reject) => { + getOutgoingCallBlocking(id).then((list) => { + resolve(list) + }).catch((err) => { + reject(err); + }); + }); +} + diff --git a/src/components/pages/Conversations/Conversations.vue b/src/components/pages/Conversations/Conversations.vue index 669306a6..1fd6452d 100644 --- a/src/components/pages/Conversations/Conversations.vue +++ b/src/components/pages/Conversations/Conversations.vue @@ -56,10 +56,15 @@ :key="item._id" :item="item" :call-available="isCallAvailable" + :blocked-incoming="blockedIncoming(item)" + :blocked-outgoing="blockedOutgoing(item)" @start-call="startCall" @download-fax="downloadFax" @download-voice-mail="downloadVoiceMail" @play-voice-mail="playVoiceMail" + @toggle-block-incoming="toggleBlockIncoming" + @toggle-block-outgoing="toggleBlockOutgoing" + @toggle-block-both="toggleBlockBoth" />
+ + + + + + + + + + + + @@ -102,7 +136,11 @@ name: 'csc-call-item', props: [ 'call', - 'callAvailable' + 'callAvailable', + 'blockIncomingLabel', + 'blockOutgoingLabel', + 'blockBothLabel', + 'blockBothPossible' ], components: { QList, @@ -192,6 +230,15 @@ startCall() { this.$refs.callPopover.close(); this.$emit('start-call', this.numberDialBack); + }, + toggleBlockIncoming() { + this.$emit('toggle-block-incoming'); + }, + toggleBlockOutgoing() { + this.$emit('toggle-block-outgoing'); + }, + toggleBlockBoth() { + this.$emit('toggle-block-both'); } } } diff --git a/src/components/pages/Conversations/CscConversationItem.vue b/src/components/pages/Conversations/CscConversationItem.vue index 7f1fe057..3fe79ede 100644 --- a/src/components/pages/Conversations/CscConversationItem.vue +++ b/src/components/pages/Conversations/CscConversationItem.vue @@ -3,7 +3,14 @@ v-if="item.type == 'call'" :call="item" :call-available="callAvailable" + :block-incoming-label="blockIncomingLabel" + :block-outgoing-label="blockOutgoingLabel" + :block-both-label="blockBothLabel" + :block-both-possible="unblockedBoth || blockedBoth" @start-call="startCall" + @toggle-block-incoming="toggleBlockIncoming" + @toggle-block-outgoing="toggleBlockOutgoing" + @toggle-block-both="toggleBlockBoth" /> @@ -30,7 +44,9 @@ name: 'csc-conversation-item', props: [ 'item', - 'callAvailable' + 'callAvailable', + 'blockedIncoming', + 'blockedOutgoing' ], components: { CscCallItem, @@ -40,6 +56,52 @@ data () { return {} }, + computed: { + number() { + if(this.item.direction === 'out') { + return this.item.callee; + } + else { + return this.item.caller; + } + }, + toggleActionIncoming() { + return this.blockedIncoming ? 'unblock' : 'block'; + }, + toggleActionOutgoing() { + return this.blockedOutgoing ? 'unblock' : 'block'; + }, + blockIncomingLabel() { + if (this.blockedIncoming) { + return this.$t('pages.conversations.buttons.unblockIncoming'); + } + else { + return this.$t('pages.conversations.buttons.blockIncoming'); + } + }, + blockOutgoingLabel() { + if (this.blockedOutgoing) { + return this.$t('pages.conversations.buttons.unblockOutgoing'); + } + else { + return this.$t('pages.conversations.buttons.blockOutgoing'); + } + }, + blockBothLabel() { + if (this.blockedBoth) { + return this.$t('pages.conversations.buttons.unblockBoth'); + } + else if (this.unblockedBoth) { + return this.$t('pages.conversations.buttons.blockBoth'); + } + }, + blockedBoth() { + return this.blockedIncoming && this.blockedOutgoing; + }, + unblockedBoth() { + return !this.blockedIncoming && !this.blockedOutgoing; + } + }, methods: { startCall(number) { this.$emit('start-call', number); @@ -52,6 +114,24 @@ }, playVoiceMail(voiceMail) { this.$emit('play-voice-mail', voiceMail); + }, + toggleBlockIncoming() { + this.$emit('toggle-block-incoming', { + number: this.number, + type: this.toggleActionIncoming + }); + }, + toggleBlockOutgoing() { + this.$emit('toggle-block-outgoing', { + number: this.number, + type: this.toggleActionOutgoing + }); + }, + toggleBlockBoth() { + this.$emit('toggle-block-both', { + number: this.number, + type: this.toggleActionIncoming + }); } } } diff --git a/src/components/pages/Conversations/CscVoiceMailItem.vue b/src/components/pages/Conversations/CscVoiceMailItem.vue index f48f35c6..8f09e456 100644 --- a/src/components/pages/Conversations/CscVoiceMailItem.vue +++ b/src/components/pages/Conversations/CscVoiceMailItem.vue @@ -87,6 +87,40 @@ :label="$t('pages.conversations.buttons.call')" /> + + + + + + + + + + + + @@ -114,7 +148,11 @@ name: 'csc-voice-mail-item', props: [ 'voiceMail', - 'callAvailable' + 'callAvailable', + 'blockIncomingLabel', + 'blockOutgoingLabel', + 'blockBothLabel', + 'blockBothPossible' ], components: { QList, @@ -174,6 +212,15 @@ startCall() { this.$refs.callPopover.close(); this.$emit('start-call', this.voiceMail.callee); + }, + toggleBlockIncoming() { + this.$emit('toggle-block-incoming'); + }, + toggleBlockOutgoing() { + this.$emit('toggle-block-outgoing'); + }, + toggleBlockBoth() { + this.$emit('toggle-block-both'); } } } diff --git a/src/locales/en.json b/src/locales/en.json index db411747..d9845b0a 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -140,7 +140,13 @@ "play": "Play", "download": "Download", "downloadFax": "Download fax", - "downloadVoicemail": "Download voicemail" + "downloadVoicemail": "Download voicemail", + "blockIncoming": "Block Incoming", + "unblockIncoming": "Unblock Incoming", + "blockOutgoing": "Block Outgoing", + "unblockOutgoing": "Unblock Outgoing", + "blockBoth": "Block Incoming/Outgoing", + "unblockBoth": "Unblock Incoming/Outgoing" }, "downloadVoiceMailSuccessMessage": "Voicemail downloaded successfully", "downloadVoiceMailErrorMessage": "Downloading of voicemail failed", @@ -161,7 +167,8 @@ "voicemail": "Voicemail", "duration": "Duration", "seconds": "seconds", - "cost": "Cost" + "cost": "Cost", + "toggledSuccessMessage": "Number {type} successfully" }, "reminder": { "toggleEnabled": "Reminder is enabled", diff --git a/src/store/conversations.js b/src/store/conversations.js index 702f7e65..9a6dbcf9 100644 --- a/src/store/conversations.js +++ b/src/store/conversations.js @@ -7,8 +7,17 @@ import { getConversations, downloadVoiceMail, downloadFax, - playVoiceMail + playVoiceMail, + getIncomingBlocked, + getOutgoingBlocked } from '../api/conversations' +import { + addNumberToIncomingList, + removeFromIncomingListByNumber, + addNumberToOutgoingList, + removeFromOutgoingListByNumber, + toggleNumberInBothLists +} from '../api/call-blocking' const ROWS_PER_PAGE = 15; @@ -62,7 +71,18 @@ export default { nextPageState: RequestState.initiated, nextPageError: null, items: [], - itemsReloaded: false + itemsReloaded: false, + blockedNumbersIncoming: new Set(), + blockedModeIncoming: null, + blockedIncomingState: RequestState.initiated, + blockedIncomingError: null, + blockedNumbersOutgoing: new Set(), + blockedModeOutgoing: null, + blockedOutgoingState: RequestState.initiated, + blockedOutgoingError: null, + toggleBlockedState: RequestState.initiated, + toggleBlockedError: null, + lastToggledType: null }, getters: { getSubscriberId(state, getters, rootState, rootGetters) { @@ -114,6 +134,70 @@ export default { }, itemsReloaded(state) { return state.itemsReloaded; + }, + isNumberIncomingBlocked(state) { + return (number) => { + if (state.blockedModeIncoming === 'whitelist') { + return !state.blockedNumbersIncoming.has(number); + } + else { + return state.blockedNumbersIncoming.has(number); + } + } + }, + isNumberOutgoingBlocked(state) { + return (number) => { + if (state.blockedModeOutgoing === 'whitelist') { + return !state.blockedNumbersOutgoing.has(number); + } + else { + return state.blockedNumbersOutgoing.has(number); + } + } + }, + blockedNumbersIncoming(state) { + return state.blockedNumbersIncoming; + }, + blockedNumbersOutgoing(state) { + return state.blockedNumbersOutgoing; + }, + blockedIncomingLoaded(state) { + return state.blockedIncomingState === RequestState.succeeded; + }, + blockedOutgoingLoaded(state) { + return state.blockedOutgoingState === RequestState.succeeded; + }, + isNumberIncomingWhitelisted(state) { + return state.blockedModeIncoming === 'whitelist'; + }, + isNumberOutgoingWhitelisted(state) { + return state.blockedModeOutgoing === 'whitelist'; + }, + actionToToggleIncomingNumber(state) { + return (number) => { + if (state.blockedNumbersIncoming.has(number)) { + return 'remove'; + } + else { + return 'add'; + } + } + }, + actionToToggleOutgoingNumber(state) { + return (number) => { + if (state.blockedNumbersOutgoing.has(number)) { + return 'remove'; + } + else { + return 'add'; + } + } + }, + toggleBlockedState(state) { + return state.toggleBlockedState; + }, + lastToggledType(state) { + return state.lastToggledType; } }, mutations: { @@ -191,6 +275,58 @@ export default { nextPageFailed(state, error) { state.nextPageState = RequestState.failed; state.nextPageError = error; + }, + blockedIncomingRequesting(state) { + state.blockedIncomingState = RequestState.requesting; + state.blockedIncomingError = null; + state.blockedModeIncoming = null; + state.blockedNumbersIncoming = new Set(); + }, + blockedIncomingSucceeded(state, options) { + let mode = options.enabled ? 'whitelist' : 'blacklist'; + let numbers = options.list ? options.list : []; + let numberSet = new Set(numbers); + state.blockedIncomingState = RequestState.succeeded; + state.blockedIncomingError = null; + state.blockedNumbersIncoming = numberSet; + state.blockedModeIncoming = mode; + }, + blockedIncomingFailed(state, error) { + state.blockedIncomingState = RequestState.failed; + state.blockedIncomingError = error; + }, + blockedOutgoingRequesting(state) { + state.blockedOutgoingState = RequestState.requesting; + state.blockedOutgoingError = null; + }, + blockedOutgoingSucceeded(state, options) { + let mode = options.enabled ? 'whitelist' : 'blacklist'; + let numbers = options.list ? options.list : []; + let numberSet = new Set(numbers); + state.blockedOutgoingState = RequestState.succeeded; + state.blockedOutgoingError = null; + state.blockedNumbersOutgoing = numberSet; + state.blockedModeOutgoing = mode; + }, + blockedOutgoingFailed(state, error) { + state.blockedOutgoingState = RequestState.failed; + state.blockedOutgoingError = error; + }, + toggleBlockedRequesting(state) { + state.toggleBlockedState = RequestState.requesting; + state.toggleBlockedError = null; + }, + toggleBlockedSucceeded(state, type) { + let typePastTense = type ? type + 'ed' : 'toggled'; + state.toggleBlockedState = RequestState.succeeded; + state.toggleBlockedError = null; + state.lastToggledType = typePastTense; + }, + toggleBlockedFailed(state, error, type) { + let typePastTense = type ? type + 'ed' : 'toggled'; + state.toggleBlockedState = RequestState.failed; + state.toggleBlockedError = error; + state.lastToggledType = typePastTense; } }, actions: { @@ -265,6 +401,116 @@ export default { context.commit('nextPageFailed', err.message); }); } + }, + getBlockedNumbersIncoming(context) { + let id = context.getters.getSubscriberId; + context.commit('blockedIncomingRequesting'); + getIncomingBlocked(id).then((data) => { + context.commit('blockedIncomingSucceeded', data); + }).catch((err)=>{ + context.commit('blockedIncomingFailed', err.message); + }); + }, + getBlockedNumbersOutgoing(context) { + let id = context.getters.getSubscriberId; + context.commit('blockedOutgoingRequesting'); + getOutgoingBlocked(id).then((data) => { + context.commit('blockedOutgoingSucceeded', data); + }).catch((err)=>{ + context.commit('blockedOutgoingFailed', err.message); + }); + }, + getBlockedNumbers(context) { + context.dispatch('getBlockedNumbersIncoming'); + context.dispatch('getBlockedNumbersOutgoing'); + }, + toggleBlockIncoming(context, options) { + let id = context.getters.getSubscriberId; + let isWhitelist = context.getters.isNumberIncomingWhitelisted; + let isBlocked = context.getters.isNumberIncomingBlocked(options.number); + context.commit('toggleBlockedRequesting'); + if ((isBlocked && isWhitelist) || (!isBlocked && !isWhitelist)) { + addNumberToIncomingList(id, options.number).then(() => { + context.commit('toggleBlockedSucceeded', options.type); + }).then(() => { + context.dispatch('getBlockedNumbersIncoming'); + }).then(() => { + context.commit('resetList'); + context.dispatch('nextPage', null); + }).catch((err) => { + context.commit('toggleBlockedFailed', err.message, options.type); + }); + } + else if ((isBlocked && !isWhitelist) || (!isBlocked && isWhitelist)) { + removeFromIncomingListByNumber(id, options.number).then(() => { + context.commit('toggleBlockedSucceeded', options.type); + }).then(() => { + context.dispatch('getBlockedNumbersIncoming'); + }).then(() => { + context.commit('resetList'); + context.dispatch('nextPage', null); + }).catch((err) => { + context.commit('toggleBlockedFailed', err.message, options.type); + }); + } + else { + context.commit('toggleBlockedFailed', 'error while identifying blocked condition', options.type); + } + }, + toggleBlockOutgoing(context, options) { + let id = context.getters.getSubscriberId; + let isWhitelist = context.getters.isNumberOutgoingWhitelisted; + let isBlocked = context.getters.isNumberOutgoingBlocked(options.number); + context.commit('toggleBlockedRequesting'); + if ((isBlocked && isWhitelist) || (!isBlocked && !isWhitelist)) { + addNumberToOutgoingList(id, options.number).then(() => { + context.commit('toggleBlockedSucceeded', options.type); + }).then(() => { + context.dispatch('getBlockedNumbersOutgoing'); + }).then(() => { + context.commit('resetList'); + context.dispatch('nextPage', null); + }).catch((err) => { + context.commit('toggleBlockedFailed', err.message, options.type); + }); + } + else if ((isBlocked && !isWhitelist) || (!isBlocked && isWhitelist)) { + removeFromOutgoingListByNumber(id, options.number).then(() => { + context.commit('toggleBlockedSucceeded', options.type); + }).then(() => { + context.dispatch('getBlockedNumbersOutgoing'); + }).then(() => { + context.commit('resetList'); + context.dispatch('nextPage', null); + }).catch((err) => { + context.commit('toggleBlockedFailed', err.message, options.type); + }); + } + else { + context.commit('toggleBlockedFailed', 'error while identifying blocked condition', options.type); + } + }, + toggleBlockBoth(context, options) { + let id = context.getters.getSubscriberId; + let inAction = context.getters.actionToToggleIncomingNumber(options.number); + let outAction = context.getters.actionToToggleOutgoingNumber(options.number); + context.commit('toggleBlockedRequesting'); + toggleNumberInBothLists({ + id: id, + number: options.number, + block_in_list: inAction, + block_out_list: outAction + }).then(() => { + context.commit('toggleBlockedSucceeded', options.type); + }).then(() => { + context.dispatch('getBlockedNumbersIncoming'); + context.dispatch('getBlockedNumbersOutgoing'); + }).then(() => { + context.commit('resetList'); + context.dispatch('nextPage', null); + }).catch((err) => { + context.commit('toggleBlockedFailed', err.message, options.type); + }); } } }; diff --git a/t/store/conversations.js b/t/store/conversations.js index 271cf2da..c02013d1 100644 --- a/t/store/conversations.js +++ b/t/store/conversations.js @@ -127,4 +127,28 @@ describe('Conversations', function(){ assert.deepEqual(state.items, data.items); }); + it('should load blocked numbers and mode', function(){ + let state = { + blockedNumbersIncoming: new Set(), + blockedModeIncoming: null, + blockedNumbersOutgoing: new Set(), + blockedModeOutgoing: null + }; + let options = { + blockAnonymous: undefined, + enabled: undefined, + list: [ + "123456", + "555555" + ] + }; + let listSet = new Set(["123456", "555555"]); + ConversationsModule.mutations.blockedIncomingSucceeded(state, options); + ConversationsModule.mutations.blockedOutgoingSucceeded(state, options); + assert.deepEqual(state.blockedNumbersIncoming, listSet); + assert.equal(state.blockedModeIncoming, 'blacklist'); + assert.deepEqual(state.blockedNumbersOutgoing, listSet); + assert.equal(state.blockedModeOutgoing, 'blacklist'); + }); + });