mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-10-31 02:57:57 +00:00 
			
		
		
		
	Feature: Add UI to update email notification preferences (#579)
This commit is contained in:
		| @@ -0,0 +1,13 @@ | |||||||
|  | import userNotificationSettings from '../userNotificationSettings'; | ||||||
|  | import ApiClient from '../ApiClient'; | ||||||
|  |  | ||||||
|  | describe('#AgentAPI', () => { | ||||||
|  |   it('creates correct instance', () => { | ||||||
|  |     expect(userNotificationSettings).toBeInstanceOf(ApiClient); | ||||||
|  |     expect(userNotificationSettings).toHaveProperty('get'); | ||||||
|  |     expect(userNotificationSettings).toHaveProperty('show'); | ||||||
|  |     expect(userNotificationSettings).toHaveProperty('create'); | ||||||
|  |     expect(userNotificationSettings).toHaveProperty('update'); | ||||||
|  |     expect(userNotificationSettings).toHaveProperty('delete'); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
							
								
								
									
										14
									
								
								app/javascript/dashboard/api/userNotificationSettings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								app/javascript/dashboard/api/userNotificationSettings.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | /* global axios */ | ||||||
|  | import ApiClient from './ApiClient'; | ||||||
|  |  | ||||||
|  | class UserNotificationSettings extends ApiClient { | ||||||
|  |   constructor() { | ||||||
|  |     super('user/notification_settings'); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   update(params) { | ||||||
|  |     return axios.patch(`${this.url}`, params); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export default new UserNotificationSettings(); | ||||||
| @@ -18,6 +18,14 @@ | |||||||
|         "TITLE": "Password", |         "TITLE": "Password", | ||||||
|         "NOTE": "Updating your password would reset your logins in multiple devices." |         "NOTE": "Updating your password would reset your logins in multiple devices." | ||||||
|       }, |       }, | ||||||
|  |       "EMAIL_NOTIFICATIONS_SECTION" : { | ||||||
|  |         "TITLE": "Email Notifications", | ||||||
|  |         "NOTE": "Update your email notification preferences here", | ||||||
|  |         "CONVERSATION_ASSIGNMENT": "Send email notifications when a conversation is assigned to me", | ||||||
|  |         "CONVERSATION_CREATION": "Send email notifications when a new conversation is created", | ||||||
|  |         "UPDATE_SUCCESS": "Your email notification preferences are updated successfully", | ||||||
|  |         "UPDATE_ERROR": "There is an error while updating the preferences, please try again" | ||||||
|  |       }, | ||||||
|       "PROFILE_IMAGE":{ |       "PROFILE_IMAGE":{ | ||||||
|         "LABEL": "Profile Image" |         "LABEL": "Profile Image" | ||||||
|       }, |       }, | ||||||
|   | |||||||
| @@ -0,0 +1,114 @@ | |||||||
|  | <template> | ||||||
|  |   <div class="profile--settings--row row"> | ||||||
|  |     <div class="columns small-3 "> | ||||||
|  |       <p class="section--title"> | ||||||
|  |         {{ $t('PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.TITLE') }} | ||||||
|  |       </p> | ||||||
|  |       <p>{{ $t('PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.NOTE') }}</p> | ||||||
|  |     </div> | ||||||
|  |     <div class="columns small-9"> | ||||||
|  |       <div> | ||||||
|  |         <input | ||||||
|  |           v-model="selectedNotifications" | ||||||
|  |           class="email-notification--checkbox" | ||||||
|  |           type="checkbox" | ||||||
|  |           value="conversation_creation" | ||||||
|  |           @input="handleInput" | ||||||
|  |         /> | ||||||
|  |         <label for="conversation_creation"> | ||||||
|  |           {{ | ||||||
|  |             $t( | ||||||
|  |               'PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.CONVERSATION_CREATION' | ||||||
|  |             ) | ||||||
|  |           }} | ||||||
|  |         </label> | ||||||
|  |       </div> | ||||||
|  |  | ||||||
|  |       <div> | ||||||
|  |         <input | ||||||
|  |           v-model="selectedNotifications" | ||||||
|  |           class="email-notification--checkbox" | ||||||
|  |           type="checkbox" | ||||||
|  |           value="conversation_assignment" | ||||||
|  |           @input="handleInput" | ||||||
|  |         /> | ||||||
|  |         <label for="conversation_assignment"> | ||||||
|  |           {{ | ||||||
|  |             $t( | ||||||
|  |               'PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.CONVERSATION_ASSIGNMENT' | ||||||
|  |             ) | ||||||
|  |           }} | ||||||
|  |         </label> | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   </div> | ||||||
|  | </template> | ||||||
|  |  | ||||||
|  | <script> | ||||||
|  | /* global bus */ | ||||||
|  | import { mapGetters } from 'vuex'; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       selectedNotifications: [], | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  |   computed: { | ||||||
|  |     ...mapGetters({ | ||||||
|  |       selectedEmailFlags: 'userNotificationSettings/getSelectedEmailFlags', | ||||||
|  |     }), | ||||||
|  |   }, | ||||||
|  |   watch: { | ||||||
|  |     selectedEmailFlags(value) { | ||||||
|  |       this.selectedNotifications = value; | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   mounted() { | ||||||
|  |     this.$store.dispatch('userNotificationSettings/get'); | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     async handleInput(e) { | ||||||
|  |       const selectedValue = e.target.value; | ||||||
|  |       if (this.selectedEmailFlags.includes(e.target.value)) { | ||||||
|  |         const selectedEmailFlags = this.selectedEmailFlags.filter( | ||||||
|  |           flag => flag !== selectedValue | ||||||
|  |         ); | ||||||
|  |         this.selectedNotifications = selectedEmailFlags; | ||||||
|  |       } else { | ||||||
|  |         this.selectedNotifications = [ | ||||||
|  |           ...this.selectedEmailFlags, | ||||||
|  |           selectedValue, | ||||||
|  |         ]; | ||||||
|  |       } | ||||||
|  |       try { | ||||||
|  |         this.$store.dispatch( | ||||||
|  |           'userNotificationSettings/update', | ||||||
|  |           this.selectedNotifications | ||||||
|  |         ); | ||||||
|  |         bus.$emit( | ||||||
|  |           'newToastMessage', | ||||||
|  |           this.$t( | ||||||
|  |             'PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.UPDATE_SUCCESS' | ||||||
|  |           ) | ||||||
|  |         ); | ||||||
|  |       } catch (error) { | ||||||
|  |         bus.$emit( | ||||||
|  |           'newToastMessage', | ||||||
|  |           this.$t( | ||||||
|  |             'PROFILE_SETTINGS.FORM.EMAIL_NOTIFICATIONS_SECTION.UPDATE_ERROR' | ||||||
|  |           ) | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  |  | ||||||
|  | <style lang="scss" scoped> | ||||||
|  | @import '~dashboard/assets/scss/variables.scss'; | ||||||
|  |  | ||||||
|  | .email-notification--checkbox { | ||||||
|  |   font-size: $font-size-large; | ||||||
|  | } | ||||||
|  | </style> | ||||||
| @@ -82,7 +82,7 @@ | |||||||
|           </label> |           </label> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |       <email-notifications /> | ||||||
|       <woot-submit-button |       <woot-submit-button | ||||||
|         class="button nice success button--fixed-right-top" |         class="button nice success button--fixed-right-top" | ||||||
|         :button-text="$t('PROFILE_SETTINGS.BTN_TEXT')" |         :button-text="$t('PROFILE_SETTINGS.BTN_TEXT')" | ||||||
| @@ -100,9 +100,11 @@ import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue'; | |||||||
| import { required, minLength, email } from 'vuelidate/lib/validators'; | import { required, minLength, email } from 'vuelidate/lib/validators'; | ||||||
| import { mapGetters } from 'vuex'; | import { mapGetters } from 'vuex'; | ||||||
| import { clearCookiesOnLogout } from '../../../../store/utils/api'; | import { clearCookiesOnLogout } from '../../../../store/utils/api'; | ||||||
|  | import EmailNotifications from './EmailNotifications'; | ||||||
|  |  | ||||||
| export default { | export default { | ||||||
|   components: { |   components: { | ||||||
|  |     EmailNotifications, | ||||||
|     Thumbnail, |     Thumbnail, | ||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
| @@ -198,7 +200,7 @@ export default { | |||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style lang="scss" scoped> | <style lang="scss"> | ||||||
| @import '~dashboard/assets/scss/variables.scss'; | @import '~dashboard/assets/scss/variables.scss'; | ||||||
| @import '~dashboard/assets/scss/mixins.scss'; | @import '~dashboard/assets/scss/mixins.scss'; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -6,8 +6,8 @@ import auth from './modules/auth'; | |||||||
| import billing from './modules/billing'; | import billing from './modules/billing'; | ||||||
| import cannedResponse from './modules/cannedResponse'; | import cannedResponse from './modules/cannedResponse'; | ||||||
| import Channel from './modules/channels'; | import Channel from './modules/channels'; | ||||||
| import contacts from './modules/contacts'; |  | ||||||
| import contactConversations from './modules/contactConversations'; | import contactConversations from './modules/contactConversations'; | ||||||
|  | import contacts from './modules/contacts'; | ||||||
| import conversationLabels from './modules/conversationLabels'; | import conversationLabels from './modules/conversationLabels'; | ||||||
| import conversationMetadata from './modules/conversationMetadata'; | import conversationMetadata from './modules/conversationMetadata'; | ||||||
| import conversationPage from './modules/conversationPage'; | import conversationPage from './modules/conversationPage'; | ||||||
| @@ -15,6 +15,7 @@ import conversations from './modules/conversations'; | |||||||
| import inboxes from './modules/inboxes'; | import inboxes from './modules/inboxes'; | ||||||
| import inboxMembers from './modules/inboxMembers'; | import inboxMembers from './modules/inboxMembers'; | ||||||
| import reports from './modules/reports'; | import reports from './modules/reports'; | ||||||
|  | import userNotificationSettings from './modules/userNotificationSettings'; | ||||||
| import webhooks from './modules/webhooks'; | import webhooks from './modules/webhooks'; | ||||||
|  |  | ||||||
| Vue.use(Vuex); | Vue.use(Vuex); | ||||||
| @@ -34,6 +35,7 @@ export default new Vuex.Store({ | |||||||
|     inboxes, |     inboxes, | ||||||
|     inboxMembers, |     inboxMembers, | ||||||
|     reports, |     reports, | ||||||
|  |     userNotificationSettings, | ||||||
|     webhooks, |     webhooks, | ||||||
|   }, |   }, | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -0,0 +1,69 @@ | |||||||
|  | import axios from 'axios'; | ||||||
|  | import { actions } from '../../userNotificationSettings'; | ||||||
|  | import * as types from '../../../mutation-types'; | ||||||
|  |  | ||||||
|  | const commit = jest.fn(); | ||||||
|  | global.axios = axios; | ||||||
|  | jest.mock('axios'); | ||||||
|  |  | ||||||
|  | describe('#actions', () => { | ||||||
|  |   describe('#get', () => { | ||||||
|  |     it('sends correct actions if API is success', async () => { | ||||||
|  |       axios.get.mockResolvedValue({ | ||||||
|  |         data: { selected_email_flags: ['conversation_creation'] }, | ||||||
|  |       }); | ||||||
|  |       await actions.get({ commit }); | ||||||
|  |       expect(commit.mock.calls).toEqual([ | ||||||
|  |         [types.default.SET_USER_NOTIFICATION_UI_FLAG, { isFetching: true }], | ||||||
|  |         [ | ||||||
|  |           types.default.SET_USER_NOTIFICATION, | ||||||
|  |           { selected_email_flags: ['conversation_creation'] }, | ||||||
|  |         ], | ||||||
|  |         [types.default.SET_USER_NOTIFICATION_UI_FLAG, { isFetching: false }], | ||||||
|  |       ]); | ||||||
|  |     }); | ||||||
|  |     it('sends correct actions if API is error', async () => { | ||||||
|  |       axios.get.mockRejectedValue({ message: 'Incorrect header' }); | ||||||
|  |       await actions.get({ commit }); | ||||||
|  |       expect(commit.mock.calls).toEqual([ | ||||||
|  |         [types.default.SET_USER_NOTIFICATION_UI_FLAG, { isFetching: true }], | ||||||
|  |         [types.default.SET_USER_NOTIFICATION_UI_FLAG, { isFetching: false }], | ||||||
|  |       ]); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   describe('#update', () => { | ||||||
|  |     it('sends correct actions if API is success', async () => { | ||||||
|  |       axios.patch.mockResolvedValue({ | ||||||
|  |         data: { selected_email_flags: ['conversation_creation'] }, | ||||||
|  |       }); | ||||||
|  |       await actions.update( | ||||||
|  |         { commit }, | ||||||
|  |         { selected_email_flags: ['conversation_creation'] } | ||||||
|  |       ); | ||||||
|  |       expect(commit.mock.calls).toEqual([ | ||||||
|  |         [types.default.SET_USER_NOTIFICATION_UI_FLAG, { isUpdating: true }], | ||||||
|  |         [ | ||||||
|  |           types.default.SET_USER_NOTIFICATION, | ||||||
|  |           { selected_email_flags: ['conversation_creation'] }, | ||||||
|  |         ], | ||||||
|  |         [types.default.SET_USER_NOTIFICATION_UI_FLAG, { isUpdating: false }], | ||||||
|  |       ]); | ||||||
|  |     }); | ||||||
|  |     it('sends correct actions if API is error', async () => { | ||||||
|  |       axios.patch.mockRejectedValue({ message: 'Incorrect header' }); | ||||||
|  |       await expect( | ||||||
|  |         actions.update( | ||||||
|  |           { commit }, | ||||||
|  |           { selected_email_flags: ['conversation_creation'] } | ||||||
|  |         ) | ||||||
|  |       ).rejects.toEqual({ | ||||||
|  |         message: 'Incorrect header', | ||||||
|  |       }); | ||||||
|  |       expect(commit.mock.calls).toEqual([ | ||||||
|  |         [types.default.SET_USER_NOTIFICATION_UI_FLAG, { isUpdating: true }], | ||||||
|  |         [types.default.SET_USER_NOTIFICATION_UI_FLAG, { isUpdating: false }], | ||||||
|  |       ]); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -0,0 +1,33 @@ | |||||||
|  | import { getters } from '../../userNotificationSettings'; | ||||||
|  |  | ||||||
|  | describe('#getters', () => { | ||||||
|  |   it('getSelectedEmailFlags', () => { | ||||||
|  |     const state = { | ||||||
|  |       record: { | ||||||
|  |         selected_email_flags: ['conversation_creation'], | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |     expect(getters.getSelectedEmailFlags(state)).toEqual([ | ||||||
|  |       'conversation_creation', | ||||||
|  |     ]); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   it('getUIFlags', () => { | ||||||
|  |     const state = { | ||||||
|  |       uiFlags: { | ||||||
|  |         fetchingList: false, | ||||||
|  |         fetchingItem: false, | ||||||
|  |         creatingItem: false, | ||||||
|  |         updatingItem: false, | ||||||
|  |         deletingItem: false, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |     expect(getters.getUIFlags(state)).toEqual({ | ||||||
|  |       fetchingList: false, | ||||||
|  |       fetchingItem: false, | ||||||
|  |       creatingItem: false, | ||||||
|  |       updatingItem: false, | ||||||
|  |       deletingItem: false, | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -0,0 +1,30 @@ | |||||||
|  | import * as types from '../../../mutation-types'; | ||||||
|  | import { mutations } from '../../userNotificationSettings'; | ||||||
|  |  | ||||||
|  | describe('#mutations', () => { | ||||||
|  |   describe('#SET_USER_NOTIFICATION', () => { | ||||||
|  |     it('set user notification record', () => { | ||||||
|  |       const state = { record: {} }; | ||||||
|  |       mutations[types.default.SET_USER_NOTIFICATION](state, { | ||||||
|  |         selected_email_flags: ['conversation_creation'], | ||||||
|  |       }); | ||||||
|  |       expect(state.record).toEqual({ | ||||||
|  |         selected_email_flags: ['conversation_creation'], | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  |  | ||||||
|  |   describe('#SET_USER_NOTIFICATION_UI_FLAG', () => { | ||||||
|  |     it('set UIFlag notification', () => { | ||||||
|  |       const state = { | ||||||
|  |         uiFlags: { isFetching: false }, | ||||||
|  |       }; | ||||||
|  |       mutations[types.default.SET_USER_NOTIFICATION_UI_FLAG](state, { | ||||||
|  |         isFetching: true, | ||||||
|  |       }); | ||||||
|  |       expect(state.uiFlags).toEqual({ | ||||||
|  |         isFetching: true, | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
| @@ -0,0 +1,77 @@ | |||||||
|  | import Vue from 'vue'; | ||||||
|  | import * as types from '../mutation-types'; | ||||||
|  | import UserNotificationSettings from '../../api/userNotificationSettings'; | ||||||
|  |  | ||||||
|  | const state = { | ||||||
|  |   record: {}, | ||||||
|  |   uiFlags: { | ||||||
|  |     isFetching: false, | ||||||
|  |     isUpdating: false, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export const getters = { | ||||||
|  |   getUIFlags($state) { | ||||||
|  |     return $state.uiFlags; | ||||||
|  |   }, | ||||||
|  |   getSelectedEmailFlags: $state => { | ||||||
|  |     return $state.record.selected_email_flags; | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export const actions = { | ||||||
|  |   get: async ({ commit }) => { | ||||||
|  |     commit(types.default.SET_USER_NOTIFICATION_UI_FLAG, { isFetching: true }); | ||||||
|  |     try { | ||||||
|  |       const response = await UserNotificationSettings.get(); | ||||||
|  |       commit(types.default.SET_USER_NOTIFICATION, response.data); | ||||||
|  |       commit(types.default.SET_USER_NOTIFICATION_UI_FLAG, { | ||||||
|  |         isFetching: false, | ||||||
|  |       }); | ||||||
|  |     } catch (error) { | ||||||
|  |       commit(types.default.SET_USER_NOTIFICATION_UI_FLAG, { | ||||||
|  |         isFetching: false, | ||||||
|  |       }); | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   update: async ({ commit }, params) => { | ||||||
|  |     commit(types.default.SET_USER_NOTIFICATION_UI_FLAG, { isUpdating: true }); | ||||||
|  |     try { | ||||||
|  |       const response = await UserNotificationSettings.update({ | ||||||
|  |         notification_settings: { | ||||||
|  |           selected_email_flags: params, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |       commit(types.default.SET_USER_NOTIFICATION, response.data); | ||||||
|  |       commit(types.default.SET_USER_NOTIFICATION_UI_FLAG, { | ||||||
|  |         isUpdating: false, | ||||||
|  |       }); | ||||||
|  |     } catch (error) { | ||||||
|  |       commit(types.default.SET_USER_NOTIFICATION_UI_FLAG, { | ||||||
|  |         isUpdating: false, | ||||||
|  |       }); | ||||||
|  |       throw error; | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export const mutations = { | ||||||
|  |   [types.default.SET_USER_NOTIFICATION_UI_FLAG]($state, data) { | ||||||
|  |     $state.uiFlags = { | ||||||
|  |       ...$state.uiFlags, | ||||||
|  |       ...data, | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  |   [types.default.SET_USER_NOTIFICATION]: ($state, data) => { | ||||||
|  |     Vue.set($state, 'record', data); | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | export default { | ||||||
|  |   namespaced: true, | ||||||
|  |   state, | ||||||
|  |   getters, | ||||||
|  |   actions, | ||||||
|  |   mutations, | ||||||
|  | }; | ||||||
| @@ -92,4 +92,8 @@ export default { | |||||||
|   SET_CURRENT_PAGE: 'SET_CURRENT_PAGE', |   SET_CURRENT_PAGE: 'SET_CURRENT_PAGE', | ||||||
|   SET_CONVERSATION_END_REACHED: 'SET_CONVERSATION_END_REACHED', |   SET_CONVERSATION_END_REACHED: 'SET_CONVERSATION_END_REACHED', | ||||||
|   CLEAR_CONVERSATION_PAGE: 'CLEAR_CONVERSATION_PAGE', |   CLEAR_CONVERSATION_PAGE: 'CLEAR_CONVERSATION_PAGE', | ||||||
|  |  | ||||||
|  |   // Notification Settings | ||||||
|  |   SET_USER_NOTIFICATION_UI_FLAG: 'SET_USER_NOTIFICATION_UI_FLAG', | ||||||
|  |   SET_USER_NOTIFICATION: 'SET_USER_NOTIFICATION', | ||||||
| }; | }; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Pranav Raj S
					Pranav Raj S