Skip to content

Commit

Permalink
Feature: Agent Profile Update with avatar (#449)
Browse files Browse the repository at this point in the history
* Feature: Agent Profile Update with avatar
* Add Update Profile with name, avatar, email and password
  • Loading branch information
pranavrajs committed Feb 16, 2020
1 parent e61ba95 commit c4e2a84
Show file tree
Hide file tree
Showing 25 changed files with 581 additions and 130 deletions.
10 changes: 5 additions & 5 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,24 +95,24 @@ jobs:
command: yarn run eslint

# Run rails tests
- run:
- run:
name: Run backend tests
command: |
bundle exec rspec $(circleci tests glob "spec/**/*_spec.rb" | circleci tests split --split-by=timings)
./tmp/cc-test-reporter format-coverage -t simplecov -o tmp/codeclimate.backend.json coverage/backend/.resultset.json
- persist_to_workspace:
root: tmp
paths:
paths:
- codeclimate.backend.json

- run:
- run:
name: Run frontend tests
command: |
yarn test:coverage
./tmp/cc-test-reporter format-coverage -t lcov -o tmp/codeclimate.frontend.json buildreports/lcov.info
- persist_to_workspace:
root: tmp
paths:
paths:
- codeclimate.frontend.json

# collect reports
Expand All @@ -126,4 +126,4 @@ jobs:
name: Upload coverage results to Code Climate
command: |
./tmp/cc-test-reporter sum-coverage tmp/codeclimate.*.json -p 2 -o tmp/codeclimate.total.json
./tmp/cc-test-reporter upload-coverage -i tmp/codeclimate.total.json
./tmp/cc-test-reporter upload-coverage -i tmp/codeclimate.total.json
1 change: 1 addition & 0 deletions __mocks__/fileMock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = '';
5 changes: 2 additions & 3 deletions app/controllers/api/v1/profiles_controller.rb
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
class Api::V1::ProfilesController < Api::BaseController
before_action :fetch_user
before_action :set_user

def show
render json: @user
end

def update
@user.update!(profile_params)
render json: @user
end

private

def fetch_user
def set_user
@user = current_user
end

Expand Down
3 changes: 1 addition & 2 deletions app/javascript/dashboard/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ export default {
},
mounted() {
this.$store.dispatch('set_user');
this.$store.dispatch('validityCheck');
this.$store.dispatch('setUser');
},
};
</script>
Expand Down
62 changes: 21 additions & 41 deletions app/javascript/dashboard/api/auth.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,10 @@
/* eslint no-console: 0 */
/* global axios */
/* eslint no-undef: "error" */
/* eslint-env browser */
/* eslint no-unused-expressions: ["error", { "allowShortCircuit": true }] */

import moment from 'moment';
import Cookies from 'js-cookie';
import endPoints from './endPoints';
import { frontendURL } from '../helper/URLHelper';

const setAuthCredentials = response => {
const expiryDate = moment.unix(response.headers.expiry);
Cookies.set('auth_data', response.headers, {
expires: expiryDate.diff(moment(), 'days'),
});
Cookies.set('user', response.data.data, {
expires: expiryDate.diff(moment(), 'days'),
});
};

const clearCookiesOnLogout = () => {
Cookies.remove('auth_data');
Cookies.remove('user');
window.location = frontendURL('login');
};
import { setAuthCredentials, clearCookiesOnLogout } from '../store/utils/api';

export default {
login(creds) {
Expand Down Expand Up @@ -60,20 +41,7 @@ export default {
},
validityCheck() {
const urlData = endPoints('validityCheck');
const fetchPromise = new Promise((resolve, reject) => {
axios
.get(urlData.url)
.then(response => {
resolve(response);
})
.catch(error => {
if (error.response.status === 401) {
clearCookiesOnLogout();
}
reject(error);
});
});
return fetchPromise;
return axios.get(urlData.url);
},
logout() {
const urlData = endPoints('logout');
Expand Down Expand Up @@ -136,13 +104,7 @@ export default {
password,
})
.then(response => {
const expiryDate = moment.unix(response.headers.expiry);
Cookies.set('auth_data', response.headers, {
expires: expiryDate.diff(moment(), 'days'),
});
Cookies.set('user', response.data.data, {
expires: expiryDate.diff(moment(), 'days'),
});
setAuthCredentials(response);
resolve(response);
})
.catch(error => {
Expand All @@ -155,4 +117,22 @@ export default {
const urlData = endPoints('resetPassword');
return axios.post(urlData.url, { email });
},

profileUpdate({ name, email, password, password_confirmation, avatar }) {
const formData = new FormData();
if (name) {
formData.append('profile[name]', name);
}
if (email) {
formData.append('profile[email]', email);
}
if (password && password_confirmation) {
formData.append('profile[password]', password);
formData.append('profile[password_confirmation]', password_confirmation);
}
if (avatar) {
formData.append('profile[avatar]', avatar);
}
return axios.put(endPoints('profileUpdate').url, formData);
},
};
3 changes: 3 additions & 0 deletions app/javascript/dashboard/api/endPoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ const endPoints = {
validityCheck: {
url: '/auth/validate_token',
},
profileUpdate: {
url: '/api/v1/profile',
},
logout: {
url: 'auth/sign_out',
},
Expand Down
25 changes: 15 additions & 10 deletions app/javascript/dashboard/components/layout/Sidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,21 @@
class="dropdown-pane top"
>
<ul class="vertical dropdown menu">
<li><a href="#" @click.prevent="logout()">Logout</a></li>
<li>
<router-link to="/app/profile/settings">
{{ $t('SIDEBAR.PROFILE_SETTINGS') }}
</router-link>
</li>
<li>
<a href="#" @click.prevent="logout()">
{{ $t('SIDEBAR.LOGOUT') }}
</a>
</li>
</ul>
</div>
</transition>
<div class="current-user" @click.prevent="showOptions()">
<thumbnail :src="gravatarUrl()" :username="currentUser.name" />
<thumbnail :src="currentUser.avatar_url" :username="currentUser.name" />
<div class="current-user--data">
<h3 class="current-user--name">
{{ currentUser.name }}
Expand All @@ -65,7 +74,6 @@

<script>
import { mapGetters } from 'vuex';
import md5 from 'md5';
import { mixin as clickaway } from 'vue-clickaway';
import adminMixin from '../../mixins/isAdmin';
Expand Down Expand Up @@ -99,6 +107,7 @@ export default {
daysLeft: 'getTrialLeft',
subscriptionData: 'getSubscription',
inboxes: 'inboxes/getInboxes',
currentUser: 'getCurrentUser',
}),
accessibleMenuItems() {
// get all keys in menuGroup
Expand Down Expand Up @@ -144,9 +153,6 @@ export default {
})),
};
},
currentUser() {
return Auth.getCurrentUser();
},
dashboardPath() {
return frontendURL('dashboard');
},
Expand Down Expand Up @@ -174,17 +180,16 @@ export default {
this.$store.dispatch('inboxes/get');
},
methods: {
gravatarUrl() {
const hash = md5(this.currentUser.email);
return `${window.WootConstants.GRAVATAR_URL}${hash}?default=404`;
},
filterBillingRoutes(menuItems) {
return menuItems.filter(
menuItem => !menuItem.toState.includes('billing')
);
},
filterMenuItemsByRole(menuItems) {
const { role } = this.currentUser;
if (!role) {
return [];
}
return menuItems.filter(
menuItem =>
window.roleWiseRoutes[role].indexOf(menuItem.toStateName) > -1
Expand Down
2 changes: 2 additions & 0 deletions app/javascript/dashboard/i18n/default-sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export default {
'inbox_conversation',
'settings_account_reports',
'billing_deactivated',
'profile_settings',
'profile_settings_index',
],
menuItems: {
assignedToMe: {
Expand Down
4 changes: 2 additions & 2 deletions app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@
},
"AUTH": {
"TITLE": "Channels",
"DESC": "Currently we support website live chat widgets and Facebook Pages as platforms. We have more platforms like Twitter, Telegram and Line in the works, which will be out soon."
"DESC": "Currently we support Website live chat widgets, Facebook Pages and Twitter profiles as platforms. We have more platforms like Whatsapp, Email, Telegram and Line in the works, which will be out soon."
},
"AGENTS": {
"TITLE": "Agents",
"DESC": "Here you can add agents to manage your newly created inbox. Only these selected agents will have access to your inbox. Agents whcih are not part of this inbox will not be able to see or respond to messages in this inbox when they login. <br> <b>PS:</b> As an administrator, if you need access to all inboxes, you should add yourself as agent to all inboxes that you create."
"DESC": "Here you can add agents to manage your newly created inbox. Only these selected agents will have access to your inbox. Agents which are not part of this inbox will not be able to see or respond to messages in this inbox when they login. <br> <b>PS:</b> As an administrator, if you need access to all inboxes, you should add yourself as agent to all inboxes that you create."
},
"DETAILS": {
"TITLE": "Inbox Details",
Expand Down
2 changes: 2 additions & 0 deletions app/javascript/dashboard/i18n/locale/en/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { default as _login } from './login.json';
import { default as _report } from './report.json';
import { default as _resetPassword } from './resetPassword.json';
import { default as _setNewPassword } from './setNewPassword.json';
import { default as _settings } from './settings.json';
import { default as _signup } from './signup.json';

export default {
Expand All @@ -24,5 +25,6 @@ export default {
..._report,
..._resetPassword,
..._setNewPassword,
..._settings,
..._signup,
};
50 changes: 50 additions & 0 deletions app/javascript/dashboard/i18n/locale/en/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
{
"PROFILE_SETTINGS": {
"LINK": "Profile Settings",
"TITLE": "Profile Settings",
"BTN_TEXT": "Update Profile",
"AFTER_EMAIL_CHANGED": "Your profile has been updated successfully, please login again as your login credentials are changed",
"FORM": {
"AVATAR": "Profile Image",
"ERROR": "Please fix form errors",
"REMOVE_IMAGE": "Remove",
"UPLOAD_IMAGE": "Upload image",
"UPDATE_IMAGE": "Update image",
"PROFILE_SECTION" : {
"TITLE": "Profile",
"NOTE": "Your email address is your identity and is used to log in."
},
"PASSWORD_SECTION" : {
"TITLE": "Password",
"NOTE": "Updating your password would reset your logins in multiple devices."
},
"PROFILE_IMAGE":{
"LABEL": "Profile Image"
},
"NAME": {
"LABEL": "Your name",
"ERROR": "Please enter a valid name",
"PLACEHOLDER": "Please enter your name, this would be displayed in conversations"
},
"EMAIL": {
"LABEL": "Your email address",
"ERROR": "Please enter a valid email address",
"PLACEHOLDER": "Please enter your email address, this would be displayed in conversations"
},
"PASSWORD": {
"LABEL": "Password",
"ERROR": "Please enter a password of length 6 or more",
"PLACEHOLDER": "Please enter a new password"
},
"PASSWORD_CONFIRMATION": {
"LABEL": "Confirm new password",
"ERROR": "Confirm password should match the password",
"PLACEHOLDER": "Please re-enter your password"
}
}
},
"SIDEBAR": {
"PROFILE_SETTINGS": "Profile Settings",
"LOGOUT": "Logout"
}
}
45 changes: 31 additions & 14 deletions app/javascript/dashboard/routes/auth/PasswordEdit.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
<template>
<form class="login-box medium-4 column align-self-middle" v-on:submit.prevent="login()">
<form
class="login-box medium-4 column align-self-middle"
@submit.prevent="login()"
>
<div class="column log-in-form">
<h4>{{$t('SET_NEW_PASSWORD.TITLE')}}</h4>
<label :class="{ 'error': $v.credentials.password.$error }">
{{$t('LOGIN.PASSWORD.LABEL')}}
<input type="password" v-bind:placeholder="$t('SET_NEW_PASSWORD.PASSWORD.PLACEHOLDER')" v-model.trim="credentials.password" @input="$v.credentials.password.$touch">
<span class="message" v-if="$v.credentials.password.$error">
{{$t('SET_NEW_PASSWORD.PASSWORD.ERROR')}}
<h4>{{ $t('SET_NEW_PASSWORD.TITLE') }}</h4>
<label :class="{ error: $v.credentials.password.$error }">
{{ $t('LOGIN.PASSWORD.LABEL') }}
<input
v-model.trim="credentials.password"
type="password"
:placeholder="$t('SET_NEW_PASSWORD.PASSWORD.PLACEHOLDER')"
@input="$v.credentials.password.$touch"
/>
<span v-if="$v.credentials.password.$error" class="message">
{{ $t('SET_NEW_PASSWORD.PASSWORD.ERROR') }}
</span>
</label>
<label :class="{ 'error': $v.credentials.confirmPassword.$error }">
{{$t('SET_NEW_PASSWORD.CONFIRM_PASSWORD.LABEL')}}
<input type="password" v-bind:placeholder="$t('SET_NEW_PASSWORD.CONFIRM_PASSWORD.PLACEHOLDER')" v-model.trim="credentials.confirmPassword" @input="$v.credentials.confirmPassword.$touch">
<span class="message" v-if="$v.credentials.confirmPassword.$error">
{{$t('SET_NEW_PASSWORD.CONFIRM_PASSWORD.ERROR')}}
<label :class="{ error: $v.credentials.confirmPassword.$error }">
{{ $t('SET_NEW_PASSWORD.CONFIRM_PASSWORD.LABEL') }}
<input
v-model.trim="credentials.confirmPassword"
type="password"
:placeholder="$t('SET_NEW_PASSWORD.CONFIRM_PASSWORD.PLACEHOLDER')"
@input="$v.credentials.confirmPassword.$touch"
/>
<span v-if="$v.credentials.confirmPassword.$error" class="message">
{{ $t('SET_NEW_PASSWORD.CONFIRM_PASSWORD.ERROR') }}
</span>
</label>
<woot-submit-button
:disabled="$v.credentials.password.$invalid || $v.credentials.confirmPassword.$invalid || newPasswordAPI.showLoading"
:disabled="
$v.credentials.password.$invalid ||
$v.credentials.confirmPassword.$invalid ||
newPasswordAPI.showLoading
"
:button-text="$t('SET_NEW_PASSWORD.SUBMIT')"
:loading="newPasswordAPI.showLoading"
button-class="expanded"
Expand Down Expand Up @@ -99,7 +116,7 @@ export default {
resetPasswordToken: this.resetPasswordToken,
};
Auth.setNewPassword(credentials)
.then((res) => {
.then(res => {
if (res.status === 200) {
window.location = res.data.redirect_url;
}
Expand Down
Loading

0 comments on commit c4e2a84

Please sign in to comment.