diff --git a/client/src/app/admin/panel/dashboard/admin-panel-my-account.js b/client/src/app/admin/panel/dashboard/admin-panel-my-account.js index 2f53b03d..336bd442 100644 --- a/client/src/app/admin/panel/dashboard/admin-panel-my-account.js +++ b/client/src/app/admin/panel/dashboard/admin-panel-my-account.js @@ -29,6 +29,7 @@ class AdminPanelMyAccount extends React.Component { profilePic: this.props.userProfilePic, level: this.props.userLevel * 1, departments: this.props.userDepartments, + sendEmailOnNewTicket: this.props.userSendEmailOnNewTicket, onChange: () => this.props.dispatch(SessionActions.getUserData(null, null, true)) }; } diff --git a/client/src/app/admin/panel/staff/staff-editor.js b/client/src/app/admin/panel/staff/staff-editor.js index a9c0f93c..9b55cff6 100644 --- a/client/src/app/admin/panel/staff/staff-editor.js +++ b/client/src/app/admin/panel/staff/staff-editor.js @@ -27,6 +27,7 @@ class StaffEditor extends React.Component { level: React.PropTypes.number.isRequired, tickets: React.PropTypes.array.isRequired, departments: React.PropTypes.array.isRequired, + sendEmailOnNewTicket: React.PropTypes.bool, onChange: React.PropTypes.func, onDelete: React.PropTypes.func }; @@ -40,7 +41,8 @@ class StaffEditor extends React.Component { level: this.props.level - 1, message: null, loadingPicture: false, - departments: this.getUserDepartments() + departments: this.getUserDepartments(), + sendEmailOnNewTicket: this.props.sendEmailOnNewTicket }; render() { @@ -95,7 +97,7 @@ class StaffEditor extends React.Component { {i18n('UPDATE_PASSWORD')} - {(!this.props.myAccount) ? this.renderLevelForm() : null} + {(this.props.myAccount) ? this.renderSendEmailOnNewTicketForm() : this.renderLevelForm()} @@ -137,6 +139,9 @@ class StaffEditor extends React.Component { case 'DEPARTMENTS': message = 'DEPARTMENTS_UPDATED'; break; + case 'EMAIL_SETTING': + message = 'EMAIL_SETTING_UPDATED'; + break; case 'FAIL': message = 'FAILED_EDIT_STAFF'; break; @@ -145,6 +150,18 @@ class StaffEditor extends React.Component { return {i18n(message)}; } + renderSendEmailOnNewTicketForm() { + return ( +
+ +
this.setState({sendEmailOnNewTicket: form.sendEmailOnNewTicket})} onSubmit={this.onSubmit.bind(this, 'EMAIL_SETTING')}> + + {i18n('UPDATE')} + +
+ ); + } + renderLevelForm() { return (
@@ -273,6 +290,7 @@ class StaffEditor extends React.Component { path: '/staff/edit', data: { staffId: this.props.staffId, + sendEmailOnNewTicket: form.sendEmailOnNewTicket, email: form.email, password: form.password, level: (form.level !== undefined) ? form.level + 1 : null, diff --git a/client/src/app/admin/panel/staff/staff-editor.scss b/client/src/app/admin/panel/staff/staff-editor.scss index f3bbd162..6ddfa0ff 100644 --- a/client/src/app/admin/panel/staff/staff-editor.scss +++ b/client/src/app/admin/panel/staff/staff-editor.scss @@ -141,10 +141,17 @@ &__update-email, &__update-password, - &__update-level { + &__update-level, + &__update-email-setting { position: relative; } + &__update-email-setting { + margin-top: 28px; + margin-bottom: 20px; + text-align: left; + } + &__departments { border: 1px solid $grey; padding: 20px 50px; diff --git a/client/src/app/main/dashboard/dashboard-create-ticket/create-ticket-form.js b/client/src/app/main/dashboard/dashboard-create-ticket/create-ticket-form.js index 35dc66fb..c04ae6b4 100644 --- a/client/src/app/main/dashboard/dashboard-create-ticket/create-ticket-form.js +++ b/client/src/app/main/dashboard/dashboard-create-ticket/create-ticket-form.js @@ -37,7 +37,7 @@ class CreateTicketForm extends React.Component { departmentIndex: 0, email: '', name: '', - language: 'en' + language: this.props.language } }; @@ -158,6 +158,7 @@ class CreateTicketForm extends React.Component { export default connect((store) => { return { + language: store.config.language, allowAttachments: store.config['allow-attachments'] }; })(CreateTicketForm); diff --git a/client/src/core-components/form-field.js b/client/src/core-components/form-field.js index c6e59b14..c1e38ef3 100644 --- a/client/src/core-components/form-field.js +++ b/client/src/core-components/form-field.js @@ -134,10 +134,10 @@ class FormField extends React.Component { if (this.props.field === 'select') { props.selectedIndex = this.props.value; - } else { - props.value = this.props.value; } + props.value = this.props.value; + return props; } diff --git a/client/src/data/fixtures/staff-fixtures.js b/client/src/data/fixtures/staff-fixtures.js index 77e47603..f86ea68a 100644 --- a/client/src/data/fixtures/staff-fixtures.js +++ b/client/src/data/fixtures/staff-fixtures.js @@ -11,6 +11,7 @@ module.exports = [ name: 'Emilia Clarke', email: 'staff@opensupports.com', profilePic: '', + sendEmailOnNewTicket: true, level: 3, staff: true, departments: [ diff --git a/client/src/data/fixtures/system-fixtures.js b/client/src/data/fixtures/system-fixtures.js index 58b95da4..50e1d5d8 100644 --- a/client/src/data/fixtures/system-fixtures.js +++ b/client/src/data/fixtures/system-fixtures.js @@ -18,6 +18,7 @@ module.exports = [ 'smtp-host': 'localhost', 'smtp-port': '7070', 'smtp-user': 'Wesa', + 'session-prefix': 'opensupports-z6ctpq2winvfhchX2_', 'maintenance-mode': false, 'allow-attachments': true, 'max-size': 500, diff --git a/client/src/data/fixtures/user-fixtures.js b/client/src/data/fixtures/user-fixtures.js index 2ff9801b..65bb8fa2 100644 --- a/client/src/data/fixtures/user-fixtures.js +++ b/client/src/data/fixtures/user-fixtures.js @@ -156,6 +156,7 @@ module.exports = [ name: 'Kurt Gödel', email: 'kurt@currycurrylady.hs', verified: false, + sendEmailOnNewTicket: true, tickets: _.times(13).map(() => { return { ticketNumber: '118551', @@ -375,6 +376,7 @@ module.exports = [ data: { name: 'Haskell Curry', email: 'haskell@lambda.com', + sendEmailOnNewTicket: true, tickets: [ { ticketNumber: '445441', diff --git a/client/src/data/languages/cn.js b/client/src/data/languages/cn.js index 278123ef..8ebd5be4 100644 --- a/client/src/data/languages/cn.js +++ b/client/src/data/languages/cn.js @@ -176,6 +176,8 @@ export default { 'HOME': '家', 'TICKET_NUMBER': '票號', 'NEXT': '下一個', + 'SEND_EMAIL_ON_NEW_TICKET': '电子邮件为每个新票', + 'UPDATE': '更新', 'CHART_CREATE_TICKET': '已創建門票', 'CHART_CLOSE': '門票已關閉', diff --git a/client/src/data/languages/de.js b/client/src/data/languages/de.js index 1ad60689..2586cf66 100644 --- a/client/src/data/languages/de.js +++ b/client/src/data/languages/de.js @@ -176,6 +176,8 @@ export default { 'HOME': 'Zuhause', 'TICKET_NUMBER': 'Ticketnummer', 'NEXT': 'Nächster', + 'SEND_EMAIL_ON_NEW_TICKET': 'Email für jedes neues Ticket', + 'UPDATE': 'Aktualisierung', 'CHART_CREATE_TICKET': 'Tickets erstellt', 'CHART_CLOSE': 'Tickets geschlossen', diff --git a/client/src/data/languages/en.js b/client/src/data/languages/en.js index 1fbf0306..1f373680 100644 --- a/client/src/data/languages/en.js +++ b/client/src/data/languages/en.js @@ -176,6 +176,8 @@ export default { 'HOME': 'Home', 'TICKET_NUMBER': 'Ticket number', 'NEXT': 'Next', + 'SEND_EMAIL_ON_NEW_TICKET': 'Send email on new ticket', + 'UPDATE': 'Update', 'CHART_CREATE_TICKET': 'Tickets created', 'CHART_CLOSE': 'Tickets closed', diff --git a/client/src/data/languages/es.js b/client/src/data/languages/es.js index fd7f8584..13273cbb 100644 --- a/client/src/data/languages/es.js +++ b/client/src/data/languages/es.js @@ -176,6 +176,8 @@ export default { 'HOME': 'Inicio', 'TICKET_NUMBER': 'Número de Ticket', 'NEXT': 'Siguiente', + 'SEND_EMAIL_ON_NEW_TICKET': 'Enviar email por cada nuevo ticket', + 'UPDATE': 'Actualizar', 'CHART_CREATE_TICKET': 'Tickets creados', 'CHART_CLOSE': 'Tickets cerrados', @@ -248,7 +250,7 @@ export default { 'TICKETS_DESCRIPTION': 'Envíe un ticket a través de nuestro centro de soporte y obtenga respuesta de sus dudas, sugerencias y problemas.', 'ARTICLES_DESCRIPTION': 'Echa un vistazo a nuestros artículos sobre temas comunes, guías y documentación.', 'ACCOUNT_DESCRIPTION': 'Todos sus tickets están almacenados en el perfil de su cuenta. Mantenga un registro de todos los tickets envíados a nuestro equipo de soporte.', - 'SUPPORT_CENTER_DESCRIPTION': 'Bienvenido a nuestro centro de soporte. Puede ponerse en contacto con nosotros a través de un sistema de tickets. Sus tickets serán contestadps por nuestro personal.', + 'SUPPORT_CENTER_DESCRIPTION': 'Bienvenido a nuestro centro de soporte. Puede ponerse en contacto con nosotros a través de un sistema de tickets. Sus tickets serán contestados por nuestro personal.', 'CUSTOM_RESPONSES_DESCRIPTION': 'Las respuestas personalizadas son respuestas automatizadas para problemas comunes.', 'MY_TICKETS_DESCRIPTION': 'Aquí puedes ver los tickets que tienes asignado.', 'NEW_TICKETS_DESCRIPTION': 'Aquí puedes ver todos los tickets nuevos que no están asignados por nadie.', diff --git a/client/src/data/languages/fr.js b/client/src/data/languages/fr.js index a1c633bd..4f012bbd 100644 --- a/client/src/data/languages/fr.js +++ b/client/src/data/languages/fr.js @@ -176,6 +176,8 @@ export default { 'HOME': 'Accueil', 'TICKET_NUMBER': 'Numéro de ticket', 'NEXT': 'Suivant', + 'SEND_EMAIL_ON_NEW_TICKET': 'Envoyer un e-mail pour chaque nouveau ticket', + 'UPDATE': 'Mettre à jour', 'CHART_CREATE_TICKET': 'Tickets créés', 'CHART_CLOSE': 'Tickets fermés', diff --git a/client/src/data/languages/in.js b/client/src/data/languages/in.js index 5e019e72..5f2092ef 100644 --- a/client/src/data/languages/in.js +++ b/client/src/data/languages/in.js @@ -176,6 +176,8 @@ export default { 'HOME': 'घर', 'TICKET_NUMBER': 'टिकट नंबर', 'NEXT': 'आगामी', + 'SEND_EMAIL_ON_NEW_TICKET': 'एक ईमेल भेजने के लिए प्रत्येक नए टिकट', + 'UPDATE': 'अद्यतन', 'CHART_CREATE_TICKET': 'टिकट बनाया', 'CHART_CLOSE': 'टिकट बंद कर दिया', diff --git a/client/src/data/languages/jp.js b/client/src/data/languages/jp.js index 0c4ac281..71edaf6e 100644 --- a/client/src/data/languages/jp.js +++ b/client/src/data/languages/jp.js @@ -176,6 +176,8 @@ export default { 'HOME': 'ホーム', 'TICKET_NUMBER': 'チケット番号', 'NEXT': '次', + 'SEND_EMAIL_ON_NEW_TICKET': 'メールを送信毎に新しいチケット', + 'UPDATE': '更新', 'CHART_CREATE_TICKET': '作成されたチケット', 'CHART_CLOSE': 'チケットが閉じられました', diff --git a/client/src/data/languages/pt.js b/client/src/data/languages/pt.js index 939500a1..2b642104 100644 --- a/client/src/data/languages/pt.js +++ b/client/src/data/languages/pt.js @@ -176,6 +176,8 @@ export default { 'HOME': 'Casa', 'TICKET_NUMBER': 'Número do bilhete', 'NEXT': 'Próximo', + 'SEND_EMAIL_ON_NEW_TICKET': 'Enviar email para cada novo ticket', + 'UPDATE': 'Actualizar', 'CHART_CREATE_TICKET': 'Ingressos criados', 'CHART_CLOSE': 'Ingressos fechados', diff --git a/client/src/data/languages/ru.js b/client/src/data/languages/ru.js index ff561236..563a3ff1 100644 --- a/client/src/data/languages/ru.js +++ b/client/src/data/languages/ru.js @@ -176,6 +176,8 @@ export default { 'HOME': 'Главная', 'TICKET_NUMBER': 'Номер билета', 'NEXT': 'следующий', + 'SEND_EMAIL_ON_NEW_TICKET': 'Отправить письмо на новый билет', + 'UPDATE': 'Обновить', 'CHART_CREATE_TICKET': 'Билеты создано', 'CHART_CLOSE': ' Билеты закрыты', diff --git a/client/src/data/languages/tr.js b/client/src/data/languages/tr.js index 0baf0487..f8d2e0b7 100644 --- a/client/src/data/languages/tr.js +++ b/client/src/data/languages/tr.js @@ -176,6 +176,8 @@ export default { 'HOME': 'Ev', 'TICKET_NUMBER': 'Bilet numarası', 'NEXT': 'Sonraki', + 'SEND_EMAIL_ON_NEW_TICKET': 'Yeni biletle e-posta gönder', + 'UPDATE': 'Güncelleştirme', 'CHART_CREATE_TICKET': 'Biletler oluşturuldu', 'CHART_CLOSE': 'Biletler kapandı', diff --git a/client/src/lib-app/__tests__/session-store-test.js b/client/src/lib-app/__tests__/session-store-test.js index 883d38a3..40f51a62 100644 --- a/client/src/lib-app/__tests__/session-store-test.js +++ b/client/src/lib-app/__tests__/session-store-test.js @@ -19,26 +19,26 @@ describe('sessionStore library', function () { it('should get, set and remove items from LocalStorage', function () { sessionStore.getItem('SOME_KEY'); - expect(LocalStorageMock.getItem).to.have.been.calledWith('SOME_KEY'); + expect(LocalStorageMock.getItem).to.have.been.calledWith(root + '_SOME_KEY'); sessionStore.setItem('SOME_KEY', 'SOME_VALUE'); - expect(LocalStorageMock.setItem).to.have.been.calledWith('SOME_KEY', 'SOME_VALUE'); + expect(LocalStorageMock.setItem).to.have.been.calledWith(root + '_SOME_KEY', 'SOME_VALUE'); sessionStore.removeItem('SOME_KEY'); - expect(LocalStorageMock.removeItem).to.have.been.calledWith('SOME_KEY'); + expect(LocalStorageMock.removeItem).to.have.been.calledWith(root + '_SOME_KEY'); }); it('should create session correctly', function () { sessionStore.createSession(14, 'TOKEN'); - expect(LocalStorageMock.setItem).to.have.been.calledWith('userId', 14); - expect(LocalStorageMock.setItem).to.have.been.calledWith('token', 'TOKEN'); + expect(LocalStorageMock.setItem).to.have.been.calledWith(root + '_userId', 14); + expect(LocalStorageMock.setItem).to.have.been.calledWith(root + '_token', 'TOKEN'); }); it('should return session data', function () { LocalStorageMock.getItem = function (key) { - if (key === 'userId') return 'USER_ID'; - if (key === 'token') return 'TOKEN'; + if (key === root + '_userId') return 'USER_ID'; + if (key === root + '_token') return 'TOKEN'; }; let sessionData = sessionStore.getSessionData(); @@ -59,8 +59,8 @@ describe('sessionStore library', function () { it('should clear session data if session is closed', function () { sessionStore.closeSession(); - expect(LocalStorageMock.removeItem).to.have.been.calledWith('userId'); - expect(LocalStorageMock.removeItem).to.have.been.calledWith('token'); + expect(LocalStorageMock.removeItem).to.have.been.calledWith(root + '_userId'); + expect(LocalStorageMock.removeItem).to.have.been.calledWith(root + '_token'); }); it('should store remember data', function () { @@ -70,26 +70,26 @@ describe('sessionStore library', function () { expiration: 20160623 }); - expect(LocalStorageMock.setItem).to.have.been.calledWith('rememberData-token', 'SOME_TOKEN'); - expect(LocalStorageMock.setItem).to.have.been.calledWith('rememberData-userId', 12); - expect(LocalStorageMock.setItem).to.have.been.calledWith('rememberData-expiration', 20160623); + expect(LocalStorageMock.setItem).to.have.been.calledWith(root + '_rememberData-token', 'SOME_TOKEN'); + expect(LocalStorageMock.setItem).to.have.been.calledWith(root + '_rememberData-userId', 12); + expect(LocalStorageMock.setItem).to.have.been.calledWith(root + '_rememberData-expiration', 20160623); }); it('should inform if remember expired', function () { - LocalStorageMock.getItem = (key) => (key === 'rememberData-expiration') ? 20160505 : null; + LocalStorageMock.getItem = (key) => (key === root + '_rememberData-expiration') ? 20160505 : null; date.getCurrentDate.returns(20160506); expect(sessionStore.isRememberDataExpired()).to.equal(true); - LocalStorageMock.getItem = (key) => (key === 'rememberData-expiration') ? 20160505 : null; + LocalStorageMock.getItem = (key) => (key === root + '_rememberData-expiration') ? 20160505 : null; date.getCurrentDate.returns(20160503); expect(sessionStore.isRememberDataExpired()).to.equal(false); }); it('should return all remember data', function () { LocalStorageMock.getItem = function (key) { - if (key === 'rememberData-userId') return 'USER_ID'; - if (key === 'rememberData-token') return 'TOKEN'; - if (key === 'rememberData-expiration') return 'EXPIRATION'; + if (key === root + '_rememberData-userId') return 'USER_ID'; + if (key === root + '_rememberData-token') return 'TOKEN'; + if (key === root + '_rememberData-expiration') return 'EXPIRATION'; }; let rememberData = sessionStore.getRememberData(); @@ -103,8 +103,8 @@ describe('sessionStore library', function () { it('should clear remember data', function () { sessionStore.clearRememberData(); - expect(LocalStorageMock.removeItem).to.have.been.calledWith('rememberData-userId'); - expect(LocalStorageMock.removeItem).to.have.been.calledWith('rememberData-token'); - expect(LocalStorageMock.removeItem).to.have.been.calledWith('rememberData-expiration'); + expect(LocalStorageMock.removeItem).to.have.been.calledWith(root + '_rememberData-userId'); + expect(LocalStorageMock.removeItem).to.have.been.calledWith(root + '_rememberData-token'); + expect(LocalStorageMock.removeItem).to.have.been.calledWith(root + '_rememberData-expiration'); }); }); \ No newline at end of file diff --git a/client/src/lib-app/session-store.js b/client/src/lib-app/session-store.js index d5d1086a..6164860f 100644 --- a/client/src/lib-app/session-store.js +++ b/client/src/lib-app/session-store.js @@ -53,6 +53,7 @@ class SessionStore { } storeConfigs(configs) { + this.setItem('session-prefix', configs['session-prefix']); this.setItem('language', configs.language); this.setItem('reCaptchaKey', configs.reCaptchaKey); this.setItem('departments', JSON.stringify(configs.departments)); @@ -106,15 +107,15 @@ class SessionStore { } getItem(key) { - return this.storage.getItem(key); + return this.storage.getItem(root + '_' + key); } setItem(key, value) { - return this.storage.setItem(key, (value !== undefined) ? value : ''); + return this.storage.setItem(root + '_' + key, (value !== undefined) ? value : ''); } removeItem(key) { - this.storage.removeItem(key); + this.storage.removeItem(root + '_' + key); } } diff --git a/client/src/reducers/config-reducer.js b/client/src/reducers/config-reducer.js index 80fafb90..d4aa5919 100644 --- a/client/src/reducers/config-reducer.js +++ b/client/src/reducers/config-reducer.js @@ -33,7 +33,11 @@ class ConfigReducer extends Reducer { } onInitConfigs(state, payload) { - const currentLanguage = sessionStore.getItem('language'); + let currentLanguage = sessionStore.getItem('language'); + + if(!_.includes(payload.data.allowedLanguages, currentLanguage)) { + currentLanguage = payload.data.language; + } sessionStore.storeConfigs(_.extend({}, payload.data, { language: currentLanguage || payload.data.language diff --git a/client/src/reducers/session-reducer.js b/client/src/reducers/session-reducer.js index a51d7e67..436516dc 100644 --- a/client/src/reducers/session-reducer.js +++ b/client/src/reducers/session-reducer.js @@ -113,7 +113,8 @@ class SessionReducer extends Reducer { userProfilePic: userData.profilePic, userLevel: userData.level, userDepartments: userData.departments, - userTickets: userData.tickets + userTickets: userData.tickets, + userSendEmailOnNewTicket: userData.sendEmailOnNewTicket }); } @@ -131,7 +132,8 @@ class SessionReducer extends Reducer { userLevel: userData.level, userDepartments: userData.departments, userTickets: userData.tickets, - userId: userId + userId: userId, + userSendEmailOnNewTicket: userData.sendEmailOnNewTicket }); }