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", | ||||
|         "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":{ | ||||
|         "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> | ||||
|         </div> | ||||
|       </div> | ||||
|  | ||||
|       <email-notifications /> | ||||
|       <woot-submit-button | ||||
|         class="button nice success button--fixed-right-top" | ||||
|         :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 { mapGetters } from 'vuex'; | ||||
| import { clearCookiesOnLogout } from '../../../../store/utils/api'; | ||||
| import EmailNotifications from './EmailNotifications'; | ||||
|  | ||||
| export default { | ||||
|   components: { | ||||
|     EmailNotifications, | ||||
|     Thumbnail, | ||||
|   }, | ||||
|   data() { | ||||
| @@ -198,7 +200,7 @@ export default { | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style lang="scss" scoped> | ||||
| <style lang="scss"> | ||||
| @import '~dashboard/assets/scss/variables.scss'; | ||||
| @import '~dashboard/assets/scss/mixins.scss'; | ||||
|  | ||||
|   | ||||
| @@ -6,8 +6,8 @@ import auth from './modules/auth'; | ||||
| import billing from './modules/billing'; | ||||
| import cannedResponse from './modules/cannedResponse'; | ||||
| import Channel from './modules/channels'; | ||||
| import contacts from './modules/contacts'; | ||||
| import contactConversations from './modules/contactConversations'; | ||||
| import contacts from './modules/contacts'; | ||||
| import conversationLabels from './modules/conversationLabels'; | ||||
| import conversationMetadata from './modules/conversationMetadata'; | ||||
| import conversationPage from './modules/conversationPage'; | ||||
| @@ -15,6 +15,7 @@ import conversations from './modules/conversations'; | ||||
| import inboxes from './modules/inboxes'; | ||||
| import inboxMembers from './modules/inboxMembers'; | ||||
| import reports from './modules/reports'; | ||||
| import userNotificationSettings from './modules/userNotificationSettings'; | ||||
| import webhooks from './modules/webhooks'; | ||||
|  | ||||
| Vue.use(Vuex); | ||||
| @@ -34,6 +35,7 @@ export default new Vuex.Store({ | ||||
|     inboxes, | ||||
|     inboxMembers, | ||||
|     reports, | ||||
|     userNotificationSettings, | ||||
|     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_CONVERSATION_END_REACHED: 'SET_CONVERSATION_END_REACHED', | ||||
|   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