diff --git a/src/api/voicebox.js b/src/api/voicebox.js new file mode 100644 index 00000000..f029b658 --- /dev/null +++ b/src/api/voicebox.js @@ -0,0 +1,52 @@ + +import _ from 'lodash' +import { + get, + patchReplace +} from './common' + +export function getVoiceboxSettings(subscriberId) { + return new Promise((resolve, reject) => { + get({ + path: `api/voicemailsettings/${subscriberId}` + }).then((result)=>{ + let settings = _.clone(result); + delete settings._links; + resolve(settings); + }).catch((err)=>{ + reject(err); + }); + }); +} + +export function setVoiceboxDelete(options) { + return patchReplace({ + path: `api/voicemailsettings/${options.subscriberId}`, + fieldPath: 'delete', + value: options.value + }); +} + +export function setVoiceboxAttach(options) { + return patchReplace({ + path: `api/voicemailsettings/${options.subscriberId}`, + fieldPath: 'attach', + value: options.value + }); +} + +export function setVoiceboxPin(options) { + return patchReplace({ + path: `api/voicemailsettings/${options.subscriberId}`, + fieldPath: 'pin', + value: options.value + }); +} + +export function setVoiceboxEmail(options) { + return patchReplace({ + path: `api/voicemailsettings/${options.subscriberId}`, + fieldPath: 'email', + value: options.value + }); +} diff --git a/src/components/layouts/Default.vue b/src/components/layouts/Default.vue index e685be26..03088688 100644 --- a/src/components/layouts/Default.vue +++ b/src/components/layouts/Default.vue @@ -232,6 +232,16 @@ + + + +
- + {{ $t('buttons.cancel') }} {{ $t('buttons.save') }} @@ -43,7 +43,7 @@ {{ number }} - {{ $t('buttons.save') }} diff --git a/src/components/pages/Conversations/Conversations.vue b/src/components/pages/Conversations/Conversations.vue index 25491f68..f1d71ea0 100644 --- a/src/components/pages/Conversations/Conversations.vue +++ b/src/components/pages/Conversations/Conversations.vue @@ -154,7 +154,7 @@ directives: { BackToTop }, - mounted() { + created() { this.$store.commit('conversations/resetList'); }, inject: ['layout'], diff --git a/src/components/pages/Conversations/CscVoiceMailItem.vue b/src/components/pages/Conversations/CscVoiceMailItem.vue index 1153dbe1..ffda1b9c 100644 --- a/src/components/pages/Conversations/CscVoiceMailItem.vue +++ b/src/components/pages/Conversations/CscVoiceMailItem.vue @@ -28,9 +28,9 @@ - {{ t$('pages.conversations.duration') }} + {{ $t('pages.conversations.duration') }} {{ voiceMail.duration }} - {{ t$('pages.conversations.seconds') }} + {{ $t('pages.conversations.seconds') }} diff --git a/src/components/pages/Voicebox/CscVoiceboxSettings.vue b/src/components/pages/Voicebox/CscVoiceboxSettings.vue new file mode 100644 index 00000000..dc4b7573 --- /dev/null +++ b/src/components/pages/Voicebox/CscVoiceboxSettings.vue @@ -0,0 +1,202 @@ + + + + + + diff --git a/src/components/pages/Voicebox/Voicebox.vue b/src/components/pages/Voicebox/Voicebox.vue new file mode 100644 index 00000000..5ad79c51 --- /dev/null +++ b/src/components/pages/Voicebox/Voicebox.vue @@ -0,0 +1,121 @@ + + + + + + diff --git a/src/locales/en.json b/src/locales/en.json index 87affda5..c51e4c7a 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -33,7 +33,8 @@ "alphaNum": "{field} must consist of numeric characters only", "inputNumber": "Input a phone number", "inputValidNumber": "Input a valid phone number", - "fieldRequiredXor": "{fieldOne} or {fieldTwo} is required" + "fieldRequiredXor": "{fieldOne} or {fieldTwo} is required", + "email": "Input a valid email address" }, "navigation": { "home": { @@ -72,6 +73,10 @@ "groups": "Groups", "seats": "Seats", "devices": "Devices" + }, + "voicebox": { + "title": "Voicebox", + "subTitle": "Set your voicebox settings" } }, "pages": { @@ -382,7 +387,8 @@ "privacyEnabledToast": "Your number is hidden to the callee", "privacyEnabledLabel": "Your number is hidden to the callee", "privacyDisabledToast": "Your number is visible to the callee", - "privacyDisabledLabel": "Your number is visible to the callee" + "privacyDisabledLabel": "Your number is visible to the callee", + "number": "Number" }, "communication": { "sendFax": "Send Fax", @@ -420,5 +426,25 @@ "addNoSlotsDialogText": "All available speed dial slots have already been assigned. Please delete one first.", "assignSlotErrorMessage": "An error occured while trying to assign the speed dial slot. Please try again.", "assignSlotSuccessMessage": "Assigned slot {slot}" + }, + "voicebox": { + "label": { + "changeEmail": "Change Email", + "changePin": "Change PIN", + "deletionEnabled": "Voicemail will be deleted after email notification is delivered", + "deletionDisabled": "Voicemail will not be deleted after email notification is delivered", + "attachmentEnabled": "Voicemail will be attached to email notification", + "attachmentDisabled": "Voicemail will not be attached to email notification" + }, + "pin": "PIN", + "loadSettingsErrorMessage": "An error occured while trying to load the settings. Please reload the page or check your network connection.", + "toggleDeleteSuccessMessage": "Toggled deletion successfully.", + "toggleDeleteErrorMessage": "An error occured while trying to toggle the delete option. Please try again.", + "toggleAttachSuccessMessage": "Toggled attachment successfully.", + "toggleAttachErrorMessage": "An error occured while trying to toggle the attach option. Please try again.", + "updatePinSuccessMessage": "Changed PIN successfully.", + "updatePinErrorMessage": "An error occured while trying to update the pin field. Please try again.", + "updateEmailSuccessMessage": "Changed email successfully.", + "updateEmailErrorMessage": "An error occured while trying to update the email field. Please try again." } } diff --git a/src/routes.js b/src/routes.js index 39bb2848..23f4a80f 100644 --- a/src/routes.js +++ b/src/routes.js @@ -14,6 +14,7 @@ import SpeedDial from './components/pages/SpeedDial/SpeedDial' import PbxConfigurationGroups from './components/pages/PbxConfiguration/CscPbxGroups' import PbxConfigurationSeats from './components/pages/PbxConfiguration/CscPbxSeats' import PbxConfigurationDevices from './components/pages/PbxConfiguration/CscPbxDevices' +import Voicebox from './components/pages/Voicebox/Voicebox'; import Login from './components/Login' import Error404 from './components/Error404' @@ -90,14 +91,16 @@ export default [ path: 'reminder', component: Reminder, meta: { - title: i18n.t('navigation.reminder.title') + title: i18n.t('navigation.reminder.title'), + subtitle: i18n.t('navigation.reminder.subTitle') } }, { path: 'speeddial', component: SpeedDial, meta: { - title: i18n.t('navigation.speeddial.title') + title: i18n.t('navigation.speeddial.title'), + subtitle: i18n.t('navigation.speeddial.subTitle') } }, { @@ -123,6 +126,14 @@ export default [ title: i18n.t('navigation.pbxConfiguration.title'), subtitle: i18n.t('navigation.pbxConfiguration.devices') } + }, + { + path: 'voicebox', + component: Voicebox, + meta: { + title: i18n.t('navigation.voicebox.title'), + subtitle: i18n.t('navigation.voicebox.subTitle') + } } ] }, diff --git a/src/store/index.js b/src/store/index.js index a6810c5f..4d5f44ce 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -13,6 +13,7 @@ import ReminderModule from './reminder' import SpeedDialModule from './speed-dial' import UserModule from './user' import CommunicationModule from './communication' +import VoiceboxModule from './voicebox' Vue.use(Vuex); @@ -27,7 +28,8 @@ export const store = new Vuex.Store({ reminder: ReminderModule, speedDial: SpeedDialModule, user: UserModule, - communication: CommunicationModule + communication: CommunicationModule, + voicebox: VoiceboxModule }, getters: { pageTitle(state) { diff --git a/src/store/reminder.js b/src/store/reminder.js index 7e6917a8..89419da3 100644 --- a/src/store/reminder.js +++ b/src/store/reminder.js @@ -10,7 +10,6 @@ import { setReminderRecurrence } from '../api/reminder'; - export default { namespaced: true, state: { diff --git a/src/store/voicebox.js b/src/store/voicebox.js new file mode 100644 index 00000000..ebe8b6ec --- /dev/null +++ b/src/store/voicebox.js @@ -0,0 +1,234 @@ + +'use strict'; + +import _ from 'lodash' +import { RequestState } from './common' +import { + getVoiceboxSettings, + setVoiceboxDelete, + setVoiceboxAttach, + setVoiceboxPin, + setVoiceboxEmail +} from '../api/voicebox'; +import { i18n } from '../i18n'; + +export default { + namespaced: true, + state: { + voiceboxSettings: { + attach: null, + delete: null, + email: '', + id: null, + pin: null, + sms_number: '' + }, + loadingState: RequestState.initial, + loadingError: null, + toggleDeleteState: RequestState.initial, + toggleDeleteError: null, + toggleAttachState: RequestState.initial, + toggleAttachError: null, + updatePinState: RequestState.initial, + updatePinError: null, + updateEmailState: RequestState.initial, + updateEmailError: null + }, + getters: { + subscriberId(state, getters, rootState, rootGetters) { + return parseInt(rootGetters['user/getSubscriberId']); + }, + isSettingsLoaded(state) { + return state.loadingState === 'succeeded'; + }, + isDeleteRequesting(state) { + return state.toggleDeleteState === 'requesting'; + }, + isAttachRequesting(state) { + return state.toggleAttachState === 'requesting'; + }, + isPinRequesting(state) { + return state.updatePinState === 'requesting'; + }, + isEmailRequesting(state) { + return state.updateEmailState === 'requesting'; + }, + loadingState(state) { + return state.loadingState; + }, + loadingError(state) { + return state.loadingError || + i18n.t('voicebox.loadSettingsErrorMessage'); + }, + voiceboxDelete(state) { + return _.get(state.voiceboxSettings, 'delete', false); + }, + voiceboxAttach(state) { + return _.get(state.voiceboxSettings, 'attach', false); + }, + deleteLabel(state) { + return state.voiceboxSettings.delete ? + i18n.t('voicebox.label.deletionEnabled') : + i18n.t('voicebox.label.deletionDisabled'); + }, + attachLabel(state) { + return state.voiceboxSettings.attach ? + i18n.t('voicebox.label.attachmentEnabled') : + i18n.t('voicebox.label.attachmentDisabled'); + }, + voiceboxSettings(state) { + return state.voiceboxSettings; + }, + toggleDeleteState(state) { + return state.toggleDeleteState; + }, + toggleDeleteError(state) { + return state.toggleDeleteError || + i18n.t('voicebox.toggleDeleteErrorMessage'); + }, + toggleAttachState(state) { + return state.toggleAttachState; + }, + toggleAttachError(state) { + return state.toggleAttachError || + i18n.t('voicebox.toggleAttachErrorMessage'); + }, + updatePinState(state) { + return state.updatePinState; + }, + updatePinError(state) { + return state.updatePinError || + i18n.t('voicebox.updatePinErrorMessage'); + }, + updateEmailState(state) { + return state.updateEmailState; + }, + updateEmailError(state) { + return state.updateEmailError || + i18n.t('voicebox.updateEmailErrorMessage'); + } + }, + mutations: { + loadingRequesting(state) { + state.loadingState = RequestState.requesting; + state.loadingError = null; + }, + loadingSucceeded(state, settings) { + state.loadingState = RequestState.succeeded; + state.voiceboxSettings = settings; + state.loadingError = null; + }, + loadingFailed(state, error) { + state.loadingState = RequestState.failed; + state.loadingError = error; + }, + toggleDeleteRequesting(state) { + state.toggleDeleteState = RequestState.requesting; + state.toggleDeleteError = null; + }, + toggleDeleteSucceeded(state) { + state.toggleDeleteState = RequestState.succeeded; + state.toggleDeleteError = null; + }, + toggleDeleteFailed(state, error) { + state.toggleDeleteState = RequestState.failed; + state.toggleDeleteError = error; + }, + toggleAttachRequesting(state) { + state.toggleAttachState = RequestState.requesting; + state.toggleAttachError = null; + }, + toggleAttachSucceeded(state) { + state.toggleAttachState = RequestState.succeeded; + state.toggleAttachError = null; + }, + toggleAttachFailed(state, error) { + state.toggleAttachState = RequestState.failed; + state.toggleAttachError = error; + }, + updatePinRequesting(state) { + state.updatePinState = RequestState.requesting; + state.updatePinError = null; + }, + updatePinSucceeded(state) { + state.updatePinState = RequestState.succeeded; + state.updatePinError = null; + }, + updatePinFailed(state, error) { + state.updatePinState = RequestState.failed; + state.updatePinError = error; + }, + updateEmailRequesting(state) { + state.updateEmailState = RequestState.requesting; + state.updateEmailError = null; + }, + updateEmailSucceeded(state) { + state.updateEmailState = RequestState.succeeded; + state.updateEmailError = null; + }, + updateEmailFailed(state, error) { + state.updateEmailState = RequestState.failed; + state.updateEmailError = error; + } + }, + actions: { + getVoiceboxSettings(context) { + context.commit('loadingRequesting'); + getVoiceboxSettings(context.getters.subscriberId).then((settings) => { + context.commit('loadingSucceeded', settings); + }).catch((err) => { + context.commit('loadingFailed', err.message); + }) + }, + toggleDelete(context) { + context.commit('toggleDeleteRequesting'); + setVoiceboxDelete({ + subscriberId: context.getters.subscriberId, + value: !context.getters.voiceboxDelete + }).then(() => { + context.commit('toggleDeleteSucceeded'); + context.dispatch('getVoiceboxSettings'); + }).catch((err) => { + context.commit('toggleDeleteFailed', err.message); + context.dispatch('getVoiceboxSettings'); + }); + }, + toggleAttach(context) { + context.commit('toggleAttachRequesting'); + setVoiceboxAttach({ + subscriberId: context.getters.subscriberId, + value: !context.getters.voiceboxAttach + }).then(() => { + context.commit('toggleAttachSucceeded'); + context.dispatch('getVoiceboxSettings'); + }).catch((err) => { + context.commit('toggleAttachFailed', err.message); + context.dispatch('getVoiceboxSettings'); + }); + }, + updatePin(context, value) { + context.commit('updatePinRequesting'); + setVoiceboxPin({ + subscriberId: context.getters.subscriberId, + value: value + }).then(() => { + context.commit('updatePinSucceeded'); + context.dispatch('getVoiceboxSettings'); + }).catch((err) => { + context.commit('updatePinFailed', err.message); + }); + }, + updateEmail(context, value) { + context.commit('updateEmailRequesting'); + setVoiceboxEmail({ + subscriberId: context.getters.subscriberId, + value: value + }).then(() => { + context.commit('updateEmailSucceeded'); + context.dispatch('getVoiceboxSettings'); + }).catch((err) => { + context.commit('updateEmailFailed', err.message); + }); + } + } +}; diff --git a/src/themes/app.common.styl b/src/themes/app.common.styl index ed76da68..d9bd22b5 100644 --- a/src/themes/app.common.styl +++ b/src/themes/app.common.styl @@ -112,3 +112,8 @@ padding-left 8px padding-right 8px +.csc-form-field + margin-bottom 40px + + .q-field-icon + color $primary diff --git a/t/api/voicebox.js b/t/api/voicebox.js new file mode 100644 index 00000000..811b19cf --- /dev/null +++ b/t/api/voicebox.js @@ -0,0 +1,80 @@ + +'use strict'; + +import Vue from 'vue'; +import VueResource from 'vue-resource'; +import { + get +} from '../../src/api/common'; +import { + getVoiceboxSettings +} from '../../src/api/voicebox'; +import { assert } from 'chai'; + +Vue.use(VueResource); + +describe('Voicebox', function(){ + + const subscriberId = 123; + + it('should get subscriber\'s voicebox settings', function(done){ + + let data = { + "_links" : { + "collection" : { + "href" : "/api/voicemailsettings/" + }, + "curies" : { + "href" : "http://purl.org/sipwise/ngcp-api/#rel-{rel}", + "name" : "ngcp", + "templated" : true + }, + "ngcp:journal" : [ + { + "href" : "/api/voicemailsettings/123/journal/" + } + ], + "ngcp:subscribers" : [ + { + "href" : "/api/subscribers/123" + } + ], + "profile" : { + "href" : "http://purl.org/sipwise/ngcp-api/" + }, + "self" : { + "href" : "/api/voicemailsettings/123" + } + }, + "attach" : true, + "delete" : false, + "email" : "", + "id" : 123, + "pin" : "1234", + "sms_number" : "" + }; + + let settings = { + "attach" : true, + "delete" : false, + "email" : "", + "id" : 123, + "pin" : "1234", + "sms_number" : "" + }; + + Vue.http.interceptors = []; + Vue.http.interceptors.unshift((request, next)=>{ + next(request.respondWith(JSON.stringify(data), { + status: 200 + })); + }); + getVoiceboxSettings(subscriberId).then((result)=>{ + assert.deepEqual(result, settings); + done(); + }).catch((err)=>{ + done(err); + }); + }); + +}); diff --git a/t/store/voicebox.js b/t/store/voicebox.js new file mode 100644 index 00000000..40e36b22 --- /dev/null +++ b/t/store/voicebox.js @@ -0,0 +1,32 @@ + +'use strict'; + +import VoiceboxModule from '../../src/store/voicebox'; +import { assert } from 'chai'; + +describe('Voicebox', function(){ + + it('should load all voicebox settings into store', function(){ + let state = { + voiceboxSettings: { + attach: null, + delete: null, + email: '', + id: null, + pin: null, + sms_number: '' + } + }; + let settings = { + attach: true, + delete: false, + email: '', + id: 123, + pin: 1234, + sms_number: '' + }; + VoiceboxModule.mutations.loadingSucceeded(state, settings); + assert.deepEqual(state.voiceboxSettings, settings); + }); + +});